Xây dựng bảy thói quen tốt về hướng đối tượng trong PHP

Làm cho các ứng dụng PHP của bạn tốt hơn với định hướng đối tượng

Với các đặc tính của ngôn ngữ hướng đối tượng (OO) của PHP, nếu bạn còn chưa tạo ra các ứng dụng của bạn với các nguyên tắc OO (object-oriented) trong tâm trí, thì bảy thói quen này sẽ giúp bạn bắt đầu quá trình chuyển tiếp giữa lập trình thủ tục và lập trình hướng đối tượng.

Nathan A. Good, Kỹ sư trưởng, tư vấn, Freelance Developer

Nathan Good sống tại vùng Twin Cities của bang Minnesota. Về chuyên môn, ông làm công việc phát triển phần mềm, kiến trúc phần mềm và quản trị các hệ thống. Khi ông không viết phần mềm, ông rất thích xây dựng các máy chủ và máy tính cá nhân, đọc và làm việc với các công nghệ mới và cố gắng khuyến khích bạn bè của mình chuyển sang phần mềm mã nguồn mở


Cấp độ đóng góp cho developerWorks của
        tác giả

13 06 2009

Trong những ngày đầu của lập trình PHP, mã PHP đã bị giới hạn là ngôn ngữ theo thủ tục về bản chất. Mã theo thủ tục (Procedural code) mô tả cách sử dụng các thủ tục cho các khối xây dựng của ứng dụng. Các thủ tục cung cấp một mức tái sử dụng nhất định khi cho phép các thủ tục được gọi bởi các thủ tục khác.

Tuy nhiên, dù không có các kết cấu của ngôn ngữ hướng đối tượng, một lập trình viên vẫn có thể đưa thêm các đặc trưng OO vào trong mã PHP. Sẽ có một chút khó khăn hơn và có thể làm cho mã khó đọc hơn bởi vì nó pha trộn các mẫu (ngôn ngữ theo thủ tục với thiết kế OO giả). Các kết cấu OO trong mã PHP — như là khả năng định nghĩa và sử dụng các lớp, khả năng xây dựng các mối quan hệ giữa các lớp có sử dụng quyền thừa kế và khả năng định nghĩa các giao diện — sẽ làm cho việc xây dựng mã tuân thủ các thói quen thực hành OO tốt dễ dàng hơn nhiều.

Trong khi các thiết kế theo thủ tục thuần túy không có nhiều tính mô đun vẫn chạy tốt, các lợi thế của thiết kế OO sẽ xuất hiện trong khâu bảo trì. Bởi vì một ứng dụng điển hình sẽ dành rất nhiều thời gian sống của nó cho việc bảo trì, bảo trì mã có chi phí rất cao trong suốt cuộc đời của một ứng dụng. Nó cũng có thể dễ dàng bị bỏ quên trong lúc phát triển. Nếu bạn đang trong một cuộc đua để làm cho ứng dụng của bạn được phát triển và triển khai, tính dễ bảo trì về dài hạn có thể tạm lùi xuống ghế sau, hãy để cho một cái gì đó bắt đầu hoạt động đã.

Tính mô đun (Modularity) — một trong những đặc trưng then chốt của thiết kế OO tốt — giúp cho việc bảo trì này. Tính mô đun giúp bao gói các thay đổi, sẽ làm cho mở rộng và sửa đổi ứng dụng theo thời gian dễ dàng hơn.

Trong khi có nhiều hơn bảy thói quen để xây dựng phần mềm OO về tổng thể, bảy thói quen sau đây là những gì bạn cần để làm cho mã của bạn phù hợp với các tiêu chuẩn thiết kế OO cơ bản. Chúng cho bạn một nền tảng vững chắc nhờ đó bạn có thể bổ sung thêm các thói quen OO và xây dựng phần mềm dễ dàng được bảo trì và được mở rộng. Các thói quen nhắm tới một số trong các đặc trưng then chốt của tính mô đun. Để biết thêm thông tin về lợi ích của thiết kế OO, độc lập với ngôn ngữ, xem Tài nguyên.

Bảy thói quen tốt về OO trong PHP là:

  1. Hãy khiêm tốn.
  2. Hãy là một láng giềng tốt.
  3. Tránh nhìn vào Medusa.
  4. Giữ vững nguyên tắc liên kết yếu nhất.
  5. Bạn là cao su, tôi là keo dán.
  6. Duy trì một gia đình.
  7. Nghĩ đến các mẫu.

Hãy khiêm tốn

Hãy khiêm tốn là để tránh tự trưng mình ra trong triển khai thực hiện các lớp và các hàm của bạn. Việc che giấu thông tin là một thói quen cơ bản. Bạn sẽ có một thời gian khó khăn để xây dựng bất kỳ các thói quen nào khác, cho đến khi bạn đã tập được thói quen che giấu các chi tiết triển khai thực hiện của bạn. Che giấu thông tin cũng được gọi là sự bao gói (encapsulation).

Có rất nhiều lý do tại sao trực tiếp để lộ ra các trường công cộng là một thói quen xấu. Điều quan trọng nhất trong số đó là nó không để lại cho bạn một tùy chọn nào nếu một cái gì đó trong triển khai thực hiện của bạn thay đổi. Bạn sử dụng các khái niệm OO để cô lập sự thay đổi và việc bao gói đóng một vai trò không thể thiếu được trong việc bảo đảm rằng bất kỳ sự thay đổi nào mà bạn thực hiện không phải là một kiểu virút trong tự nhiên. Những thay đổi kiểu Virút là những thay đổi bắt đầu là nhỏ — giống như việc thay đổi một mảng chứa ba phần tử thành chỉ chứa có hai. Đột nhiên, bạn nhận thấy rằng bạn đang thay đổi càng ngày càng nhiều mã của bạn để thích nghi với một thay đổi lẽ ra là không đáng kể.

Một cách đơn giản để bắt đầu che giấu thông tin của bạn là giữ các trường riêng tư (private) và chỉ để lộ chúng thông qua các hàm truy cập công cộng, giống như các cửa sổ trong nhà bạn. Thay vì có toàn bộ cả một bức tường mở toang ra bên ngoài, thì bạn chỉ có một hoặc hai cửa sổ. (Tôi nói thêm về hàm truy cập công cộng trong "Thói quen tốt: Sử dụng hàm truy cập công cộng").

Ngoài việc cho phép các triển khai thực hiện của bạn diễn ra sau bức màn đối với các thay đổi, việc sử dụng những hàm truy cập công cộng thay vì trực tiếp để lộ các trường cho phép bạn xây dựng thêm dựa trên triển khai thực hiện nền tảng của bạn bằng cách đè lên (overriding) triển khai thực hiện của một hàm truy cập để làm một cái gì đó hơi khác với hành vi ứng xử của cha mẹ. Nó cũng cho phép bạn xây dựng một triển khai thực hiện trừu tượng (abstract) để trì hoãn việc triển khai thực hiện thực tế các lớp đè lên nền tảng đã có.

Thói quen xấu: Để lộ các trường công cộng

Trong ví dụ mã xấu trong Liệt kê 1, các trường của các đối tượng Person được lộ ra trực tiếp như là các trường công cộng thay vì qua các hàm truy cập. Trong khi hành vi này rất cám dỗ, đặc biệt là cho các đối tượng dữ liệu ít quan trọng, thì nó lại hạn chế bạn.

Liệt kê 1. Thói quen xấu về việc để lộ các trường công cộng
<?php
class Person
{
    public $prefix;
    public $givenName;
    public $familyName;
    public $suffix;
}

$person = new Person();
$person->prefix = "Mr.";
$person->givenName = "John";

echo($person->prefix);
echo($person->givenName);

?>

Nếu có bất kỳ điều gì thay đổi với một đối tượng, bất kỳ mã nào sử dụng nó cũng cần phải thay đổi theo. Ví dụ, nếu họ, tên, tên đệm của một người đã được bao bọc trong một đối tượng PersonName, bạn sẽ cần phải sửa đổi tất cả các mã của bạn cho phù hợp với sự thay đổi đó.

Thói quen tốt: Sử dụng các hàm truy cập công cộng

Bằng cách sử dụng các thói quen OO tốt (xem Liệt kê 2), cùng một đối tượng bây giờ có các trường riêng tư thay cho các trường công cộng và các trường riêng tư được trưng ra một cách thận trọng với thế giới bên ngoài bằng các phương thức getset công cộng, được gọi là những hàm truy cập (accessors). Những hàm truy cập bây giờ cung cấp một cách công cộng để nhận thông tin từ lớp PHP của bạn sao cho nếu có thay đổi gì trong các triển khai thực hiện của bạn, thì ít có khả năng là bạn cần phải thay đổi tất cả các mã đã sử dụng lớp ấy.

Liệt kê 2. Thói quen tốt sử dụng những hàm truy cập công cộng
<?php
class Person
{
    private $prefix;
    private $givenName;
    private $familyName;
    private $suffix;
    
    public function setPrefix($prefix)
    {
        $this->prefix = $prefix;
    }
    
    public function getPrefix()
    {
        return $this->prefix;
    }
    
    public function setGivenName($gn)
    {
        $this->givenName = $gn;
    }
    
    public function getGivenName()
    {
        return $this->givenName;
    }
    
    public function setFamilyName($fn)
    {
        $this->familyName = $fn;
    }
    
    public function getFamilyName() 
    {
        return $this->familyName;
    }
    
    public function setSuffix($suffix)
    {
        $this->suffix = $suffix;
    }
    
    public function getSuffix()
    {
        return $suffix;
    }
    
}

$person = new Person();
$person->setPrefix("Mr.");
$person->setGivenName("John");

echo($person->getPrefix());
echo($person->getGivenName());

?>

Thoạt nhìn điều này có vẻ sẽ thêm nhiều công việc hơn và có thể thực sự là nhiều việc hơn ở mặt trước. Tuy nhiên, thường việc áp dụng thói quen OO tốt sẽ được đền bù lại trong lâu dài về sau, bởi vì các thay đổi trong tương lai được củng cố chắc chắn.

Trong phiên bản mã được hiển thị trong Liệt kê 3, tôi đã thay đổi việc triển khai thực hiện bên trong để sử dụng một mảng kết hợp cho các phần của tên. Lý tưởng ra, phải có nhiều xử lý lỗi hơn và kiểm tra cẩn thận hơn xem phần tử đó có tồn tại không, nhưng mục đích của ví dụ này là để cho thấy làm thế nào để các mã sử dụng lớp của tôi không cần phải thay đổi — thật hạnh phúc là nó không biết được về sự thay đổi lớp của tôi. Hãy nhớ rằng lý do để chấp nhận các thói quen OO là bao gói cẩn thận các thay đổi để cho mã của bạn có khả năng mở rộng hơn và dễ bảo trì hơn.

Liệt kê 3. Một sửa đổi khác theo thói quen tốt này với việc triển khai thực hiện bên trong khác đi
<?php
class Person
{
    private $personName = array();
    
    public function setPrefix($prefix)
    {
        $this->personName['prefix'] = $prefix;
    }
    
    public function getPrefix()
    {
        return $this->personName['prefix'];
    }
    
    public function setGivenName($gn)
    {
        $this->personName['givenName'] = $gn;
    }
    
    public function getGivenName()
    {
        return $this->personName['givenName'];
    }

    /* etc... */
}

/*
 * Even though the internal implementation changed, the code here stays exactly
 * the same. The change has been encapsulated only to the Person class.
 */
$person = new Person();
$person->setPrefix("Mr.");
$person->setGivenName("John");

echo($person->getPrefix());
echo($person->getGivenName());

?>

Hãy là một láng giềng tốt

Khi bạn xây dựng một lớp, nó cần xử lý lỗi riêng của mình một cách thích hợp. Nếu lớp không biết làm thế nào để xử lý các lỗi, nó cần gói chúng theo một định dạng mà người gọi nó hiểu được. Ngoài ra, tránh trả về các đối tượng không tồn tại (null) hay trong trạng thái không hợp lệ. Nhiều trường hợp, bạn có thể làm điều này đơn giản bằng cách kiểm tra các đối số và đưa ra các ngoại lệ cụ thể để nói lý do tại sao các đối số được cung cấp không hợp lệ. Khi bạn xây dựng thói quen này, nó có thể tiết kiệm cho bạn — và những người đang bảo trì mã của bạn hoặc đang sử dụng các đối tượng của bạn — rất nhiều thời gian.

Thói quen xấu: Không xử lý các lỗi

Xem xét ví dụ hiển thị trong Liệt kê 4, trong đó chấp nhận một số đối số và trả về một đối tượng Person với một số các giá trị đã được điền vào. Tuy nhiên, trong phương thức parsePersonName() không có xác nhận hợp lệ để xem liệu biến $val được cung cấp là bằng null, là một chuỗi ký tự có chiều dài bằng không hay là một chuỗi ký tự mà khuôn dạng sai, không phân giải được. Phương thức parsePersonName() không trả về một đối tượng Person mà trả về null. Các quản trị viên hoặc các lập trình viên sử dụng phương thức này có thể phải vò đầu bứt tai và — ít nhất cũng là — đi đến chỗ phải bắt đầu thiết lập các điểm ngắt và gỡ rối kịch bản lệnh PHP.

Liệt kê 4. Thói quen xấu không đưa ra hay không xử lý các lỗi
class PersonUtils
{
    public static function parsePersonName($format, $val)
    {
        if (strpos(",", $val) > 0) {
            $person = new Person();
            $parts = split(",", $val); // Assume the value is last, first
            $person->setGivenName($parts[1]);
            $person->setFamilyName($parts[0]);
        }
        return $person;
    }
}

Phương thức parsePersonName() trong Liệt kê 4 có thể được sửa đổi để khởi tạo đối tượng Person bên ngoài điều kiện if, khi đảm bảo rằng bạn luôn luôn nhận một đối tượng Person hợp lệ. Tuy nhiên, bạn sẽ nhận được một Person mà các thuộc tính còn chưa được thiết lập, điều này cũng không đưa bạn đến một vị trí tốt hơn.

Thói quen tốt: Mỗi mô đun xử lý các lỗi riêng của nó

Thay vì để mặc cho người gọi lớp của bạn phỏng đoán, hãy chủ động kiểm tra tính hợp lệ của các đối số. Nếu một biến chưa thiết lập không thể tạo ra một kết quả hợp lệ, hãy kiểm tra biến và đưa ra một InvalidArgumentException. Nếu chuỗi ký tự không thể trống rỗng hoặc phải có một định dạng cụ thể, hãy kiểm tra định dạng và đưa ra một ngoại lệ. Liệt kê 5 biểu thị làm thế nào để tạo ra các ngoại lệ của riêng bạn, cũng như một số điều kiện mới trong phương thức parsePerson() để giải thích một số các xác nhận hợp lệ ban đầu.

Liệt kê 5. Thói quen tốt đưa thêm các báo lỗi
<?php
class InvalidPersonNameFormatException extends LogicException {}


class PersonUtils
{
    public static function parsePersonName($format, $val)
    {
        if (! $format) {
            throw new InvalidPersonNameFormatException("Invalid PersonName format.");
        }

        if ((! isset($val)) || strlen($val) == 0) {
            throw new InvalidArgumentException("Must supply a non-null value to parse.");
        }


    }
}
?>

Dòng dưới đáy là cái mà bạn muốn mọi người có thể sử dụng lớp của bạn mà không phải biết các hoạt động bên trong của nó. Nếu họ sử dụng nó không đúng hoặc theo cách mà bạn đã không dự định, họ không phải phỏng đoán lý do tại sao nó không hoạt động. Với tư cách là một láng giềng tốt, bạn hiểu rằng mọi người sử dụng lại lớp của bạn mà không phải là các thầy bói và do đó bạn nên loại bỏ việc phỏng đoán đó.


Tránh nhìn vào Medusa

Khi tôi lần đầu tiên học về các khái niệm OO, tôi đã nghi ngờ rằng các giao diện là thực sự có ích hay không. Một đồng nghiệp của tôi đã nêu ra phép so sánh việc không sử dụng các giao diện giống như đang nhìn vào cái đầu của Medusa. Trong thần thoại Hy Lạp, Medusa là một quỷ cái có bộ tóc là những con rắn. Bất kỳ người nào mà nhìn thẳng vào nó sẽ biến thành đá. Perseus, người đã giết Medusa, đã có thể đối phó với con quỷ bằng cách nhìn vào hình ảnh phản chiếu của nó trong cái khiên của anh, vì vậy không bị biến thành đá.

Giao diện là tấm gương của bạn để đối phó với Medusa. Khi bạn sử dụng một triển khai thực hiện cụ thể, rõ ràng, mã của bạn phải thay đổi nếu mã triển khai thực hiện của bạn thay đổi. Việc sử dụng trực tiếp các triển khai thực hiện sẽ hạn chế các tùy chọn của bạn, giống như bạn đã biến các lớp của bạn thành đá.

Thói quen xấu: Không sử dụng các giao diện

Liệt kê 6 hiển thị một ví dụ để nạp đối tượng Person từ một cơ sở dữ liệu. Nó nhận tên person và trả về đối tượng Person phù hợp lấy từ cơ sở dữ liệu.

Liệt kê 6. Thói quen xấu không sử dụng các giao diện
<?php
class DBPersonProvider
{
    public function getPerson($givenName, $familyName)
    {
        /* go to the database, get the person... */
        $person = new Person();
        $person->setPrefix("Mr.");
        $person->setGivenName("John");
        return $person;
    }
}

/* I need to get person data... */
$provider = new DBPersonProvider();
$person = $provider->getPerson("John", "Doe");

echo($person->getPrefix());
echo($person->getGivenName());

?>

Mã để nạp Person từ cơ sở dữ liệu là tốt cho đến khi có một số điều gì đó thay đổi trong môi trường. Ví dụ, nạp Person từ cơ sở dữ liệu có thể là tốt cho phiên bản đầu tiên của ứng dụng, nhưng đối với phiên bản thứ hai, bạn có thể cần phải thêm các khả năng để nạp person từ một dịch vụ web. Về bản chất, lớp đã biến thành đá vì nó đang trực tiếp sử dụng lớp triển khai thực hiện và bây giờ rất dễ hỏng nếu thay đổi.

Thói quen tốt: Sử dụng các giao diện

Liệt kê 7 cho thấy một ví dụ về mã mà sẽ không thay đổi khi một cách nạp mới trở thành sẵn có cho người sử dụng và được triển khai thực hiện. Ví dụ cho thấy một giao diện được gọi là PersonProvider, có khai báo chỉ một phương thức. Nếu mã bất kỳ sử dụng PersonProvider, thì mã đó sẽ bị ngăn cản không được sử dụng trực tiếp các lớp triển khai thực hiện. Thay vào đó, nó sử dụng PersonProvider giống như đó là một đối tượng thực sự.

Liệt kê 7. Thói quen tốt về sử dụng các giao diện
<?php
interface PersonProvider
{
    public function getPerson($givenName, $familyName);
}

class DBPersonProvider implements PersonProvider 
{
    public function getPerson($givenName, $familyName)
    {
        /* pretend to go to the database, get the person... */
        $person = new Person();
        $person->setPrefix("Mr.");
        $person->setGivenName("John");
        return $person;
    }
}

class PersonProviderFactory
{
    public static function createProvider($type)
    {
        if ($type == 'database')
        {
            return new DBPersonProvider();
        } else {
            return new NullProvider();
        }
    }
}

$config = 'database';
/* I need to get person data... */
$provider = PersonProviderFactory::createProvider($config);
$person = $provider->getPerson("John", "Doe");

echo($person->getPrefix());
echo($person->getGivenName());
?>

Khi bạn sử dụng các giao diện, cố gắng tránh tham chiếu trực tiếp đến các lớp triển khai thực hiện. Thay vào đó, hãy sử dụng một cái gì đó bên ngoài đối tượng của bạn để cung cấp cho bạn việc triển khai thực hiện đúng. Nếu lớp của bạn nạp việc triển khai thực hiện dựa trên một số logic, nó vẫn cần yêu cầu các định nghĩa của tất cả các lớp triển khai thực hiện và điều này sẽ không đưa bạn tiến thêm bao nhiêu.

Bạn có thể sử dụng một mẫu nhà máy (Factory) để tạo ra một cá thể của một lớp triển khai thực hiện giao diện của bạn. Một phương thức factory, theo quy ước, bắt đầu bằng việc tạo và trả về một giao diện. Nó có thể nhận bất kỳ đối số nào cần thiết cho phương thức factory của bạn để tìm ra lớp triển khai thực hiện nào là một lớp đúng để trả về.

Trong Liệt kê 7, phương thức createProvider() đơn giản chỉ dùng một biến $type. Nếu $type được thiết lập là cơ sở dữ liệu, phương thức database, sẽ trả về một cá thể của DBPersonProvider. Bất kỳ việc triển khai thực hiện mới để nạp nhiều Person từ một kho lưu trữ không cần bất kỳ sự thay đổi nào trong lớp có sử dụng phương thức Factory và giao diện. DBPersonProvider thực hiện giao diện PersonProvider và có triển khai thực hiện thực tế phương thức getPerson() bên trong nó.


Lắp ghép lỏng các môđun của bạn là một việc tốt; nó là một trong các tính chất để cho phép bạn đóng gói sự thay đổi. Hai trong số các thói quen khác — "Hãy khiêm tốn" và "Tránh nhìn vào Medusa" — giúp bạn làm việc theo hướng xây dựng các mô đun được lắp ghép lỏng. Để lắp ghép lỏng các lớp của bạn, hãy phát triển đặc tính cuối cùng bằng cách xây dựng thói quen về việc làm giảm sự phụ thuộc giữa các lớp của bạn.

Thói quen xấu: Lắp ghép chặt

Trong Liệt kê 8, việc làm giảm sự phụ thuộc không nhất thiết là làm giảm các sự phụ thuộc đối với trình khách sử dụng một đối tượng. Thay vào đó, ví dụ minh hoạ việc làm giảm các sự phụ thuộc vào một lớp chính xác và tối thiểu sự phụ thuộc này đối với các lớp khác.

Liệt kê 8. Thói quen xấu về lắp ghép chặt từ đối tượng Address
<?php

require_once "./AddressFormatters.php";

class Address
{
    private $addressLine1;
    private $addressLine2;
    private $city;
    private $state; // or province...
    private $postalCode;
    private $country;

    public function setAddressLine1($line1)
    {
        $this->addressLine1 = $line1;
    }

		/* accessors, etc... */

    public function getCountry()
    {
        return $this->country;
    }

    public function format($type)
    {
        if ($type == "inline") {
            $formatter = new InlineAddressFormatter();
        } else if ($type == "multiline") {
            $formatter = new MultilineAddressFormatter();
        } else {
            $formatter = new NullAddressFormatter();
        }
        return $formatter->format($this->getAddressLine1(), 
            $this->getAddressLine2(), 
            $this->getCity(), $this->getState(), $this->getPostalCode(), 
            $this->getCountry());
    }
}

$addr = new Address();
$addr->setAddressLine1("123 Any St.");
$addr->setAddressLine2("Ste 200");
$addr->setCity("Anytown");
$addr->setState("AY");
$addr->setPostalCode("55555-0000");
$addr->setCountry("US");

echo($addr->format("multiline"));
echo("\n");

echo($addr->format("inline"));
echo("\n");

?>

Đoạn mã gọi phương thức format() của một đối tượng Address có thể trông rất ấn tượng — tất cả mọi việc mà nó làm là sử dụng lớp Address, gọi phương thức format(), và thế là xong. Ngược lại, lớp Address không may mắn như vậy. Nó cần phải biết nhiều trình định dạng (formatters) khác nhau được sử dụng để định dạng đúng. Điều này có thể làm cho đối tượng Address không có khả năng sử dụng lại với những người khác, đặc biệt là nếu một người nào khác không quan tâm đến việc sử dụng các lớp của trình định dạng trong phương thức format(). Mặc dù mã đang sử dụng Address hiện không có nhiều sự phụ thuộc, nhưng lớp Address lại có một số phụ thuộc, trong khi nó lẽ ra chỉ là một đối tượng dữ liệu đơn giản.

Lớp Address được lắp ghép chặt với các lớp triển khai thực hiện để biết cách làm thế nào để định dạng một đối tượng Address.

Thói quen tốt: Lắp ghép lỏng giữa các đối tượng

Khi xây dựng các thiết kế OO tốt, cần suy nghĩ về một khái niệm gọi là Phân tách các mối quan tâm (Separation of Concerns-SoC). SoC có nghĩa là bạn cố gắng để phân tách các đối tượng theo cái mà chúng thực sự dính líu đến, do đó, làm lỏng việc lắp ghép. Trong lớp Address ban đầu, nó đã phải quan tâm đến việc làm thế nào để định dạng chính nó. Điều đó có lẽ không phải là một thiết kế tốt. Thay vào đó, một lớp Address cần lo lắng về các phần của Address, trong khi một số kiểu trình định dạng lo lắng về việc làm thế nào để định dạng đúng các địa chỉ.

Trong Liệt kê 9, mã định dạng địa chỉ được di chuyển đến các giao diện, các lớp triển khai thực hiện và một nhà máy (factory) — hãy nhớ xây dựng thói quen "sử dụng các giao diện". Bây giờ, lớp AddressFormatUtils có trách nhiệm tạo ra một trình định dạng và định dạng một Address. Bất kỳ đối tượng khác nào bây giờ có thể sử dụng một Address mà không cần phải lo lắng về việc phải có được định nghĩa về các trình định dạng.

Liệt kê 9. Thói quen tốt về lắp ghép lỏng giữa các đối tượng
<?php

interface AddressFormatter
{
    public function format($addressLine1, $addressLine2, $city, $state, 
        $postalCode, $country);
}

class MultiLineAddressFormatter implements AddressFormatter 
{
    public function format($addressLine1, $addressLine2, $city, $state, 
        $postalCode, $country)
    {
        return sprintf("%s\n%s\n%s, %s %s\n%s", 
            $addressLine1, $addressLine2, $city, $state, $postalCode, $country);
    }
}

class InlineAddressFormatter implements AddressFormatter 
{
    public function format($addressLine1, $addressLine2, $city, $state, 
        $postalCode, $country)
    {
        return sprintf("%s %s, %s, %s %s %s", 
            $addressLine1, $addressLine2, $city, $state, $postalCode, $country);
    }
}

class AddressFormatUtils 
{
    public static function formatAddress($type, $address)
    {
        $formatter = AddressFormatUtils::createAddressFormatter($type);
        
        return $formatter->format($address->getAddressLine1(), 
            $address->getAddressLine2(), 
            $address->getCity(), $address->getState(), 
            $address->getPostalCode(), 
            $address->getCountry());
    }
    
    private static function createAddressFormatter($type)
    {
        if ($type == "inline") {
            $formatter = new InlineAddressFormatter();
        } else if ($type == "multiline") {
            $formatter = new MultilineAddressFormatter();
        } else {
            $formatter = new NullAddressFormatter();
        }
        return $formatter;
    }
}

$addr = new Address();
$addr->setAddressLine1("123 Any St.");
$addr->setAddressLine2("Ste 200");
$addr->setCity("Anytown");
$addr->setState("AY");
$addr->setPostalCode("55555-0000");
$addr->setCountry("US");

echo(AddressFormatUtils::formatAddress("multiline", $addr));
echo("\n");

echo(AddressFormatUtils::formatAddress("inline", $addr));
echo("\n");
?>

Mặt hạn chế, tất nhiên, là bất cứ khi nào sử dụng các mẫu hình sẵn, thường cũng có nghĩa là số lượng các tạo phẩm (các lớp, các tệp tin) tăng lên. Tuy nhiên, điều này được bù đắp bằng việc giảm công sức bảo trì trong mỗi lớp và có thể được giảm nhiều hơn nữa nếu có được khả năng sử dụng lại đúng cách.


Bạn là cao su; tôi là keo dán

Các thiết kế OO kết dính cao là rất tập trung và được tổ chức thành các mô đun liên quan. Tìm hiểu kỹ "các mối quan tâm" là rất quan trọng trong việc xác định tổ chức các hàm và các lớp như thế nào để kết dính chặt chẽ.

Thói quen xấu: Kết dính thấp

Khi một thiết kế có kết dính thấp, nó có các lớp và các phương thức không được nhóm lại thích hợp. Thuật ngữ mã spaghetti thường được sử dụng để mô tả các lớp và các phương thức được gói chung với nhau và có kết dính thấp. Liệt kê 10 cung cấp một ví dụ về mã Spaghetti. Lớp tổng quát Utils sử dụng nhiều đối tượng khác nhau và có rất nhiều sự phụ thuộc. Nó làm mỗi thứ một tí, làm cho khó có thể sử dụng lại.

Liệt kê 10. Thói quen xấu về kết dính thấp
<?php

class Utils
{
    public static function formatAddress($formatType, $address1, 
        $address2, $city, $state)
    {
        return "some address string";
    }
    
    public static function formatPersonName($formatType, $givenName, 
        $familyName)
    {
        return "some person name";
    }
    
    public static function parseAddress($formatType, $val)
    {
        // real implementation would set values, etc...
        return new Address();
    }
    
    public static function parseTelephoneNumber($formatType, $val)
    {
        // real implementation would set values, etc...
        return new TelephoneNumber();
    }
}

?>

Thói quen tốt: Giữ vững nguyên tắc kết dính cao

Kết dính cao có nghĩa là các lớp và các phương thức có liên quan với nhau được nhóm lại. Khi các phương thức và các lớp có kết dính cao, bạn có thể dễ dàng tách ra toàn bộ nhóm mà không ảnh hưởng đến thiết kế. Các thiết kế có kết dính cao tạo ra cơ hội cho lắp ghép lỏng. Liệt kê 11 hiển thị hai trong số các phương thức được tổ chức tốt hơn thành lớp. Lớp AddressUtils có chứa các phương thức để làm việc với các lớp Address và cho thấy sự kết dính cao giữa các phương thức có liên quan đến địa chỉ. Tương tự, PersonUtils chứa các phương thức để làm việc riêng với các đối tượng Person. Hai lớp mới này với các phương thức kết dính cao của chúng sẽ lắp ghép lỏng vì chúng có thể được sử dụng hoàn toàn độc lập với nhau.

Liệt kê 11. Thói quen tốt về kết dính cao
<?php

class AddressUtils
{
    public static function formatAddress($formatType, $address1, 
        $address2, $city, $state)
    {
        return "some address string";
    }
    
    public static function parseAddress($formatType, $val)
    {
        // real implementation would set values, etc...
        return new Address();
    }
    
}

class PersonUtils
{
    public static function formatPersonName($formatType, $givenName, 
        $familyName)
    {
        return "some person name";
    }
    
    public static function parsePersonName($formatType, $val)
    {
        // real implementation would set values, etc...
        return new PersonName();
    }
}

?>

Duy trì một gia đình

Tôi thường nói với mọi người trong các nhóm phần mềm, mà tôi làm lãnh kỹ thuật đạo hay kiến trúc sư rằng kẻ thù lớn nhất của các ngôn ngữ OO là hành động sao chép và dán. Khi chưa có thiết kế OO chuẩn bị trước, không có gì đẻ ra nhiều lộn xộn hơn là việc sao chép mã từ một tệp tin này vào tệp khác. Bất cứ khi nào mà bạn định sao chép mã từ một lớp này tới một lớp tiếp theo, hãy dừng lại và xem xét làm sao có thể sử dụng các hệ thống thứ bậc của lớp để sử dụng các chức năng tương tự hay giống nhau. Bạn sẽ thấy rằng trong nhiều trường hợp, với thiết kế tốt, việc sao chép mã là hoàn toàn không cần thiết.

Thói quen xấu: Không sử dụng hệ thống thứ bậc các lớp

Liệt kê 12 cho thấy một ví dụ đơn giản của các lớp bộ phận. Chúng bắt đầu với các trường và các phương thức giống hệt — về lâu dài sẽ không tốt khi ứng dụng có thể phải thay đổi. Nếu có một khiếm khuyết trong lớp Person có nhiều khả năng cũng sẽ là một khiếm khuyết trong lớp Employee vì nó xuất hiện qua triển khai thực hiện bằng cách sao chép giữa hai lớp.

Liệt kê 12. Thói quen xấu không sử dụng các hệ thống thứ bậc
<?php
class Person
{
    private $givenName;
    private $familyName;
}

class Employee
{
    private $givenName;
    private $familyName;
}

?>

Quyền thừa kế là một thói quen khó bắt đầu áp dụng vì thông thường, việc phân tích để xây dựng các mô hình quyền thừa kế đúng đắn có thể mất nhiều thời gian. Ngược lại, sử dụng Ctrl+CCtrl+V để xây dựng một triển khai thực hiện mới chỉ mất vài giây. Tuy nhiên, khoảng thời gian này thường rất nhanh được bù đắp lại trong bảo trì, nơi ứng dụng sẽ thực tế trải qua hầu hết phần đời của nó.

Thói quen tốt: Sử dụng quyền thừa kế

Trong Liệt kê 13, lớp Employee mới mở rộng lớp Person Bây giờ nó kế thừa tất cả các phương thức chung và không triển khai thực hiện lại chúng. Ngoài ra, Liệt kê 13 cho thấy việc sử dụng một phương thức trừu tượng để giải thích rằng chức năng cơ bản có thể được đưa vào một lớp cơ sở và chức năng cụ thể hơn có thể để lui lại cho đến khi triển khai thực hiện một lớp.

Liệt kê 13. Thói quen tốt về sử dụng quyền thừa kế
<?php
abstract class Person
{
    private $givenName;
    private $familyName;
    
    public function setGivenName($gn)
    {
        $this->givenName = $gn;
    }
    
    public function getGivenName()
    {
        return $this->givenName;
    }
    
    public function setFamilyName($fn)
    {
        $this->familyName = $fn;
    }
    
    public function getFamilyName()
    {
        return $this->familyName;
    }
     
    public function sayHello()
    {
        echo("Hello, I am ");
        $this->introduceSelf();
    }
    
    abstract public function introduceSelf();
    
}

class Employee extends Person
{
    private $role;
    
    public function setRole($r)
    {
        $this->role = $r; 
    }
    
    public function getRole()
    {
        return $this->role;
    }
    
    public function introduceSelf()
    {
        echo($this->getRole() . " " . $this->getGivenName() . " " . 
            $this->getFamilyName());
    }
}
?>

Nghĩ đến các mẫu

Các mẫu thiết kế là các tương tác phổ biến giữa các đối tượng và các phương thức mà đã được thời gian kiểm chứng là giải quyết tốt những bài toán cụ thể. Khi bạn nghĩ về các mẫu hình thiết kế, tức là bạn đang bắt buộc mình lĩnh hội việc các lớp tương tác với nhau như thế nào. Đó là một cách dễ dàng để xây dựng các lớp và các tương tác của chúng mà không mắc phải các lỗi giống như những người khác đã mắc phải trong quá khứ và hưởng lợi từ các thiết kế đã được kiểm chứng

Thói quen xấu: Xem xét mỗi lần một đối tượng

Thực sự là không có ví dụ mã thích hợp nào để giải thích được nghĩ đến các mẫu hình là như thế nào (mặc dù có nhiều ví dụ tốt cho thấy các triển khai thực hiện theo mẫu hình). Tuy nhiên, nói chung, bạn sẽ biết bạn đang xem xét riêng lẻ từng đối tượng mỗi lần khi các dấu hiệu sau đây là đúng:

  • Bạn không vẽ sơ đồ một mô hình đối tượng trước đó.
  • Bạn bắt đầu viết mã triển khai các phương thức đơn lẻ mà chưa vạch ra những nét gốc rễ của mô hình.
  • Bạn không sử dụng đến các tên mẫu hình thiết kế khi nói mà đúng hơn là nói về triển khai thực hiện.

Thói quen tốt: Thêm các đối tượng, một cách hài hòa, phối hợp thành mẫu

Nói chung, bạn đã nghĩ đến các mẫu khi:

  • Mô hình hóa các lớp và các tương tác của chúng sẵn từ trước.
  • Tạo bản mẫu các lớp (stereotype) theo các mẫu hình của chúng.
  • Sử dụng các tên mẫu, như Factory, SingletonFacade.
  • Vạch ra phần lớn gốc rễ của mô hình, sau đó bắt đầu thêm triển khai thực hiện.

Kết luận

Xây dựng các thói quen tốt OO trong PHP giúp bạn xây dựng các ứng dụng ổn định hơn, dễ bảo trì hơn và có khả năng mở rộng dễ dàng hơn. Ghi nhớ:

  • Hãy khiêm tốn.
  • Hãy là một láng giềng tốt.
  • Tránh nhìn vào Medusa.
  • Giữ vững nguyên tắc liên kết yếu nhất.
  • Bạn là cao su, tôi là keo dán.
  • Duy trì một gia đình.
  • Nghĩ đến các mẫu.

Khi bạn đã xây dựng và có được các thói quen này, bạn có thể bất ngờ về các thay đổi về chất lượng của các ứng dụng của bạn.

Tài nguyên

Học tập

Lấy sản phẩm và công nghệ

  • Đổi mới dự án phát triển mã nguồn mở tiếp theo của bạn với phần mềm dùng thử của IBM, có sẵn để tải về hoặc trên đĩa DVD.
  • Tải về các phiên bản đánh giá sản phẩm IBM, và nhận các sản phẩm phần mềm trung gian và các công cụ phát triển ứng dụng thực hành từ DB2®, Lotus®, Rational®, Tivoli® và WebSphere®.

Thảo luận

Bình luận

developerWorks: Đăng nhập

Các trường được đánh dấu hoa thị là bắt buộc (*).


Bạn cần một ID của IBM?
Bạn quên định danh?


Bạn quên mật khẩu?
Đổi mật khẩu

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Ở lần bạn đăng nhập đầu tiên vào trang developerWorks, một hồ sơ cá nhân của bạn được tạo ra. Thông tin trong bản hồ sơ này (tên bạn, nước/vùng lãnh thổ, và tên cơ quan) sẽ được trưng ra cho mọi người và sẽ đi cùng các nội dung mà bạn đăng, trừ khi bạn chọn việc ẩn tên cơ quan của bạn. Bạn có thể cập nhật tài khoản trên trang IBM bất cứ khi nào.

Thông tin gửi đi được đảm bảo an toàn.

Chọn tên hiển thị của bạn



Lần đầu tiên bạn đăng nhập vào trang developerWorks, một bản trích ngang được tạo ra cho bạn, bạn cần phải chọn một tên để hiển thị. Tên hiển thị của bạn sẽ đi kèm theo các nội dung mà bạn đăng tải trên developerWorks.

Tên hiển thị cần có từ 3 đến 30 ký tự. Tên xuất hiện của bạn phải là duy nhất trên trang Cộng đồng developerWorks và vì lí do an ninh nó không phải là địa chỉ email của bạn.

Các trường được đánh dấu hoa thị là bắt buộc (*).

(Tên hiển thị cần có từ 3 đến 30 ký tự)

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Thông tin gửi đi được đảm bảo an toàn.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=70
Zone=Nguồn mở
ArticleID=396430
ArticleTitle=Xây dựng bảy thói quen tốt về hướng đối tượng trong PHP
publish-date=06132009