Семь хороших объектно-ориентированных привычек при программировании на PHP

Улучшите свои PHP-приложения за счет объектно-ориентированного подхода

Если при создании своих приложений на языке PHP вы пока не пользуетесь его объектно-ориентированными возможностями, семь полезных привычек помогут вам начать переход от процедурного программирования к объектно-ориентированному программированию

Натан А. Гуд, инженер по программному обеспечению, консультант

Натан Гуд (Nathan A. Good) живет в регионе Twin Cities (штат Миннесота, США). Н. Гуд профессионально занимается разработкой программного обеспечения, архитектурой программного обеспечения и системным администрированием. Когда Н. Гуд не занят написанием программного обеспечения, он с удовольствием занимается построением ПК и серверов, изучением и освоением новых технологий, а также убеждением своих коллег в необходимости перехода на программное обеспечение с открытым кодом. Натан Гуд лично и в соавторстве написал множество книг и статей, в том числе: Professional Red Hat Enterprise Linux 3, Regular Expression Recipes: A Problem-Solution Approach, Regular Expression Recipes for Windows Developers: A Problem-Solution Approach, PHP 5 Recipes: A Problem-Solution Approach. Самая новейшая из его работ: Foundations of PEAR: Rapid PHP Development.



22.06.2009

На начальном этапе развития языка программирования PHP присущие ему ограничения делали PHP-код исключительно процедурным. Процедурный код – это программный код, в котором в качестве стандартных блоков для построения приложения применяются процедуры. Процедуры предполагают определенный уровень повторного использования благодаря возможности их вызова другими процедурами.

Тем не менее, даже без специальных объектно-ориентированных языковых конструкций программист может реализовать в PHP-коде некоторые принципы ООП. Подобное смешение парадигм (процедурный язык с ОО-элементами) несколько затрудняет работу программиста и может осложнить чтение программного кода. ОО-конструкции в PHP-коде — позволяющие создавать и применять классы, строить отношения между классами с использованием наследования, определять интерфейсы — существенно упрощают написание программного кода, соответствующего методикам ООП.

В то время как чисто процедурные проекты без большой степени модульности ведут себя достаточно хорошо на этапе исполнения, преимущества ООП-подхода проявляются при последующем сопровождении. Поскольку у типичного приложения большая часть жизни приходится на период его эксплуатации, сопровождение программного кода приложения выливается в большие расходы за срок его службы. Кроме того, об этих аспектах можно легко забыть во время разработки. Если вам необходимо как можно быстрее создать и развернуть свое приложение, то последующее удобство сопровождения в долгосрочной перспективе может отойти на второй план по сравнению с более актуальной задачей – достижением немедленной работоспособности.

Модульность – это одна из ключевых характеристик хорошего ОО-проекта, оказывающая существенное содействие на этапе его сопровождения. Модульность помогает инкапсулировать изменения, что облегчает последующее расширение и модификацию приложения.

Несмотря на то, что общее количество «полезных привычек» при построении ОО-программ существенно превышает число 7, описываемые в данной статье привычки – это именно то, что необходимо вам для приведения своего программного кода в соответствие с базовыми критериями ООП. Эти семь привычек образуют устойчивый фундамент, на котором вы сможете строить свои следующие ОО-подходы и создавать программное обеспечение, которое будет достаточно простым в обслуживании и в расширении. Эти привычки нацелены на несколько ключевых аспектов модульности. В разделе Ресурсы приведены ссылки на дополнительную информацию о преимуществах ОО-проектирования, независимых от применяемого языка.

Итак, семь хороших объектно-ориентированных привычек при программировании на PHP:

  1. Будьте скромны.
  2. Будьте хорошим соседом.
  3. Не смотрите на Медузу Горгону.
  4. Применяйте самое слабое связывание.
  5. Обеспечьте высокое сцепление.
  6. Поддерживайте семейственность.
  7. Мыслите шаблонами.

Будьте скромны

В данном случае быть скромным – значит избегать выставления своей информации в своих реализациях классов и функций. Сокрытие своей информации должно стать основополагающей привычкой. Вы будете испытывать трудности при выработке всех остальных привычек до тех пор, пока не привыкнете скрывать детали своей реализации. Для сокрытия информации есть еще один термин – инкапсуляция.

Вследствие множества причин непосредственное выставление полей в виде public является плохой привычкой; и самая важная из этих причин следующая: вы лишаете себя возможности что-либо изменить в своей реализации в случае необходимости. Концепции ООП используются для того, чтобы изолировать изменения; инкапсуляция играет здесь центральную роль – она гарантирует, что любые вносимые вами изменения не будут вирулентными по своей природе. Вирулентные изменения начинаются с малого, например, с преобразования трехэлементного массива в массив, содержащий всего два элемента. Внезапно вы обнаруживаете, что для того, чтобы адаптироваться к этому, казалось бы, тривиальному изменению, вам приходится вносить в свой код все больше и больше других изменений.

Один простой способ для перехода к сокрытию своей информации состоит в том, чтобы оставлять поля в статусе private и выставлять их с помощью public методов-аксессоров, которые ведут себя как окна в вашем доме. Вместо того чтобы открывать наружу всю стену, вы используете всего одно или два окна. (Более подробно аксессоры рассматриваются в разделе «Хорошая привычка: использование public-аксессоров»).

Использование public-аксессоров вместо непосредственного выставления полей позволяет вам менять свою реализацию «за занавесом». Кроме того, это позволяет вам продлить использование своей базовой реализации посредством переопределения реализации аксессора, чтобы он смог делать нечто отличающееся от поведения своего родителя. Это также позволяет строить абстрактные реализации, которое отодвигают фактическую реализацию до момента создания классов, переопределяющих базовую реализацию.

Плохая привычка: выставление полей как public

В примере «плохого» кода в листинге 1 аксессоры не применяются, а поля объекта Person выставлены непосредственно как поля public. Хотя такое поведение и заманчиво, особенно в случае «легковесных» объектов данных, оно существенно ограничивает ваши возможности.

Листинг 1. Плохая привычка: выставление полей как public
<?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);

?>

Если в объекте что-либо меняется, то любой код, который использует его, также должен измениться. Например, если имя, фамилия и другие атрибуты определенного человека необходимо инкапсулировать в объект PersonName, то для адаптации к этому изменению вам придется модифицировать весь свой код.

Хорошая привычка: использование public-аксессоров

При использовании хороших ОО-привычек (см. листинг 2) тот же самый объект теперь имеет поля типа private (вместо полей public), и эти поля private с соблюдением мер предосторожности выставлены внешнему миру с помощью методов get и set, так называемых методов-акессоров. Теперь эти аксессоры обеспечивают публичный способ получения информации от вашего класса PHP. В случае каких-либо изменений в вашей реализации это существенно уменьшит вероятность того, что вам придется изменять весь код, использующий этот класс.

Листинг 2. Хорошая привычка: использование public-аксессоров
<?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());

?>

На первый взгляд может показаться, что это существенно увеличивает объем работы, особенно на фронтальной части. Следует, однако, отметить, что в большинстве случаев использование хороших ОО-привычек в конечном счете окупается, поскольку существенно упрощает будущие изменения.

В версии кода, показанной в листинге 3, я изменил внутреннюю реализацию, использовав ассоциативный массив для отдельных частей имени. В идеале мне следовало бы уделить больше внимания обработке ошибок и соблюдать большую осторожность, в частности, проверять элементы на существование, однако у этого примера несколько иные цели: я хочу показать, что код, использующий мой класс, не нуждается в изменениях — он находится в блаженном неведении об изменениях в моем классе. Помните, что причина для принятия хороших ОО-привычек состоит в том, чтобы тщательно инкапсулировать изменения и таким образом улучшить расширяемость и сопровождаемость кода.

Листинг 3. Другая разновидность этой хорошей привычки с иной внутренней реализацией
<?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());

?>

Будьте хорошим соседом

Когда вы создаете какой-либо класс, он должен обрабатывать свои собственные ошибки надлежащим образом. Если класс не знает, как обращаться с такими ошибками, он должен упаковывать их в формате, понятном для вызвавшего его класса. Кроме того избегайте возвращать объекты с состоянием null или invalid. Во многих случаях этого можно добиться посредством простой верификации аргументов и выдачи определенных исключений, объясняющих, почему предоставленные аргументы недопустимы. Когда вы усвоите эту привычку, она поможет сэкономить много времени и вам, и тем, кто будет сопровождать ваш код или использовать ваши объекты.

Плохая привычка: отсутствие обработки ошибок

Рассмотрим показанный в листинге 4 пример, в котором принимаются некоторые аргументы и возвращается объект Person с некоторыми заполненными значениями. В методе parsePersonName() отсутствует какая-либо проверка на предмет того, не имеет ли предоставляемая переменная $val состояния null и не является ли она строкой нулевой длины или строкой в неподдерживаемом формате. Метод parsePersonName() не возвращает объект Person, но возвращает null. Администраторам или программистам, использующим этот метод, придется надолго задуматься и, как минимум, заняться введением контрольных точек и отладкой этого PHP-скрипта.

Листинг 4. Плохая привычка: отсутствие выдачи или обработки ошибок
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;
    }
}

Метод parsePersonName() в листинге 4 можно изменить таким образом, чтобы инициализировать объект Person за пределами условия if. Это гарантирует, что вы всегда будете получать объект Person в допустимом состоянии. Однако при этом вы получаете объект Person без установленных свойств, что ненамного улучшает ваше положение.

Хорошая привычка: каждый модуль сам обрабатывает свои ошибки

Вместо того чтобы заставлять своих «вызывающих» строить догадки, позаботьтесь заранее о проверке аргументов. Если не установленное значение переменной может привести к недостоверному результату, проверьте эту переменную и выдайте исключение InvalidArgumentException. Если строка не может быть пустой или должна быть представлена в определенном формате, проверьте ее на соответствие этому формату и выдайте исключение. В листинге 5 показано, как создавать свои собственные исключения, а также продемонстрированы новые условия в методе parsePerson(), которые осуществляют некоторые элементарные проверки.

Листинг 5. Хорошая привычка: выдача ошибок
<?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.");
        }


    }
}
?>

Практический результат состоит в том, что ваш класс смогут использовать другие люди, при этом им не потребуются знания о том, что происходит внутри этого класса. Если эти люди станут использовать ваш класс неправильным или не предназначенным для него способом, им не придется гадать о том, почему он не работает. Как хороший сосед, вы отдаете себе отчет в том, что люди, которые будут повторно использовать ваш класс, - не телепаты, и поэтому избавляете их от необходимости догадок.


Не смотрите на Медузу Горгону

Когда я впервые изучал концепции ООП, у меня были определенные сомнения относительно того, являются ли интерфейсы действительно полезными. Мой коллега предложил следующую аналогию: не использовать интерфейсы – это то же самое, что смотреть в лицо Медузы Горгоны. Согласно древнегреческой мифологии, Медуза Горгона – это женское существо со змеями вместо волос. Любой человек, который смотрел на нее непосредственно, превращался в камень. Персей, который убил Медузу, смог противостоять ей, глядя на ее отражение в своем отполированном щите, и таким образом избежав превращения в камень.

Интерфейсы – это ваше зеркало при обращении с Медузой Горгоной. Если вы применяете специализированную, «негибкую» реализацию, то в случае изменения этой реализации ваш код также должен будет измениться. Непосредственное использование реализаций ограничивает перечень доступных вам вариантов, поскольку, образно говоря, вы обратили свои классы в камень.

Плохая привычка: отсутствие интерфейсов

В примере в листинге 6 объект Person загружается из базы данных. В частности, осуществляется прием имени человека (Person) и возвращение объекта Person в соответствующую базу данных.

Листинг 6. Плохая привычка: отсутствие интерфейсов
<?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());

?>

Код для загрузки Person из базы данных работает прекрасно до тех пор, пока в окружающей среде ничего не меняется. Например, загрузка Person из базы данных может эффективно удовлетворять потребности первой версии приложения, однако в его второй версии вам, возможно, придется добавить возможность загрузки информации из Web-сервиса. По существу, ваш класс «окаменел», поскольку он непосредственно использует класс реализации и не рассчитан на изменения.

Хорошая привычка: использование интерфейсов

В листинге 7 показан пример кода, который не нуждается в изменениях в случае появления и реализации новых способов загрузки пользователей. В примере показан интерфейс с именем PersonProvider, который объявляет единственный метод. Если какой-либо код использует интерфейс PersonProvider, то этот код будет изолирован от непосредственного использования классов реализации. Вместо этого указанный код будет использовать интерфейс PersonProvider, как будто это реальный объект.

Листинг 7. Хорошая привычка: использование интерфейсов
<?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());
?>

При использовании интерфейсов старайтесь избегать непосредственного обращения к классам реализации. Вместо этого для корректной реализации используйте нечто «внешнее» по отношению к своему объекту. Если ваш класс загружает реализацию на основе определенной логики, то ему по-прежнему придется потребовать определения для всех классов реализации. Таким образом, вы остались на прежнем месте.

С помощью шаблона Factory вы можете создать экземпляр класса реализации, который и реализует ваш интерфейс. В соответствии с принятыми соглашениями, метод factory начинается со слова create и возвращает интерфейс. Он может принять любые аргументы, необходимые для вашего шаблона factory, чтобы выяснить, какой класс реализации является корректным для возврата.

В листинге 7 метод createProvider() просто принимает значение $type. Если переменной $type присваивается значение database, то factory возвращает экземпляр DBPersonProvider. Любая новая реализация для загрузки данных о людях из какого-либо хранилища не потребует каких-либо изменений в классе, который использует шаблон factory и интерфейс. Класс DBPersonProvider реализует интерфейс PersonProvider и содержит в себе фактическую реализацию метода getPerson().


Слабое связывание модулей является весьма хорошим приемом; слабое связывание – это одно из свойств, которые позволяют инкапсулировать изменения. Две рассмотренные выше привычки – «скромность» и «использование интерфейса для работы с Медузой Горгоной» – помогают создавать именно слабо связанные модули. Чтобы добиться слабого связывания своих классов, выработайте привычку к ослаблению их зависимостей.

Плохая привычка: сильное связывание

Как показано в листинге 8, ослабление зависимостей – это не обязательно ослабление зависимостей для клиента, использующего тот или иной объект. Скорее, в этом примере демонстрируется ослабление зависимостей для соответствующего класса и сведение их к минимуму в остальных местах.

Листинг 8. Плохая привычка: сильное связывание
<?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");

?>

Код, который вызывает метод format() в объекте Address, может выглядеть прекрасно: все, что ему нужно сделать – использовать класс Address и вызвать метод format(). Задачи самого класса Address несколько сложнее. Чтобы осуществить форматирование надлежащим образом, ему необходимо знать о различных используемых «форматерах». Это ограничивает возможности повторного использования объекта Address кем-либо еще, особенно если этот «кто-то» не заинтересован в использовании классов formatter в методе format(). Хотя код, использующий класс Address, не имеет большого числа зависимостей, у самого класса Address довольно много зависимостей, при том, что ему, по всей вероятности, следовало бы быть простым объектом данных.

Класс Address сильно связан с классами реализации, которые «знают», как форматировать объект Address.

Хорошая привычка: слабое связывание между объектами

При построении хороших ОО-проектов необходимо придерживаться концепции «разделения ответственности» (Separation of Concerns, SoC). В соответствии с этой концепцией объекты разделяются по возлагаемой на них ответственности, что существенно ослабляет связывание между ними. В исходной ситуации на класс Address возлагается ответственность за осуществление форматирования. Вероятно, это не очень хороший подход. Вместо этого классу Address следовало бы заботиться о частях адреса, в то время как за надлежащее форматирование адреса должен был бы отвечать соответствующий форматер (formatter).

Как показано в листинге 9, код, который ранее форматировал адрес, теперь перемещен в интерфейсы, в классы реализации и в factory — в соответствии с привычкой к «использованию интерфейсов». Теперь класс AddressFormatUtils отвечает за создание форматера и за форматирование адреса. Объект Address теперь может быть использован любым другим объектом, без какого-либо беспокойства об определении требований к форматеру.

Листинг 9. Хорошая привычка: слабое связывание между объектами
<?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");
?>

Оборотная сторона этого подхода состоит в том, что при любом использовании шаблонов возрастает количество артефактов (классов, файлов). Тем не менее эта тенденция компенсируется уменьшением объема сопровождения в каждом классе, причем степень этой компенсации значительно возрастает при достижении надлежащего уровня многократного использования.


Обеспечьте высокое сцепление

ОО-проекты с т.н. «высоким сцеплением» (high cohesion) отличаются сфокусированностью и организованностью в виде модулей с внутренним родством. Знание «ответственности» (см. предыдущий раздел) позволяет организовать функции и классы таким образом, чтобы они обладали высоким сцеплением.

Плохая привычка: низкое сцепление

Низкое сцепление проекта означает, что его классы и методы сгруппированы ненадлежащим образом. Для описания классов и методов, которые объединены друг с другом, но при этом имеют низкое сцепление, часто используется термин спагетти-код. Пример спагетти-кода показан в листинге 10. Сравнительно универсальный (generic) класс Utils использует несколько различных объектов и имеет несколько зависимостей. Он делает «всего понемногу», что существенно затрудняет его повторное использование.

Листинг 10. Плохая привычка: низкое сцепление
<?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();
    }
}

?>

Хорошая привычка: высокое сцепление

Высокое сцепление проекта означает, что родственные классы и методы объединены в группы. Если методы и классы обладают высоким сцеплением, вы сможете легко выделить их из состава группы без ущерба для всего проекта. Проекты с высоким сцеплением создают условия для ослабления связности. В листинге 11 показана более эффективная организация методов в классы. Класс AddressUtils содержит методы для работы с классами Address, благодаря чему обеспечивается высокое сцепление методов, имеющих отношение к адресу. Аналогично, класс PersonUtils содержит методы, которые имеют дело только с объектами Person. Эти два новых класса, методы внутри которых имеют высокое сцепление, сами обладают низкой взаимной связностью, поскольку могут быть использованы совершенно независимо друг от друга.

Listing 11. Хорошая привычка: высокое сцепление
<?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();
    }
}

?>

Поддерживайте семейственность

Участникам групп разработки программного обеспечения, в которых я являюсь техническим руководителем или архитектором, я часто повторяю, что основной враг ОО-языков – операция «копировать-вставить». Ничто другое не способно нанести большего вреда, чем копирование кода из предыдущего файла в следующий файл – без заранее подготовленного ОО-проекта. Каждый раз, когда вы испытываете искушение скопировать код из одного класса в другой, остановитесь и подумайте, каким образом вы могли бы использовать иерархии классов для реализации подобных или идентичных функциональных возможностей. Вы обнаружите, что в большинстве случаев при наличии хорошего проекта в копировании кода нет абсолютно никакой необходимости.

Плохая привычка: отсутствие использования иерархий классов

В листинге 12 показан простой пример частичных классов. В этих классах применяются дублирующиеся поля и методы — что не очень хорошо в долгосрочной перспективе, когда, возможно, придется модифицировать данное приложение. Если в классе Person имеется дефект, то вероятнее всего, аналогичный дефект имеется и в классе Employee, поскольку он, по-видимому, создан копированием первого класса (или наоборот).

Листинг 12. Плохая привычка: отсутствие использования иерархий классов
<?php
class Person
{
    private $givenName;
    private $familyName;
}

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

?>

Выработать привычку к использованию наследования трудно, поскольку во многих случаях анализ, необходимый для построения надлежащих моделей наследования, может отнять много времени. И наоборот, использование Ctrl+C и Ctrl+V для построения новой реализации занимает всего несколько секунд. Следует, однако, отметить, что обычно потраченное время окупается достаточно быстро на этапе сопровождения, который фактически занимает большую часть жизненного цикла приложения.

Хорошая привычка: использование наследования

В листинге 13 показан новый класс Employee, расширяющий класс Person. Он теперь наследует все общие методы, не реализуя их повторно. Кроме того, в листинге 13 показано, как использование абстрактного метода позволяет поместить базовую функциональность в базовый класс, а специфическую – расположить внутри класса реализации.

Листинг 13. Хорошая привычка: использование наследования
<?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());
    }
}
?>

Мыслите шаблонами

Шаблоны проектирования – это проверенные временем типовые взаимодействия объектов и методов при решении определенных проблем. Когда вы мыслите шаблонами проектирования, вы заставляете себя осознавать, как классы взаимодействуют друг с другом. Шаблоны – это удобный способ построения классов и их взаимодействий без повторения всех тех ошибок, которые уже сделали в прошлом другие. Кроме того, они позволяют воспользоваться проверенными конструкциями.

Плохая привычка: рассмотрение отдельно взятых объектов

Не существует каких-либо действительно адекватных примеров кода, которые позволили бы продемонстрировать мышление в шаблонах (хотя есть масса хороших примеров, демонстрирующих реализацию шаблонов). В общем случае можно говорить, что вы рассматриваете каждый объект в отдельности, если соблюдаются следующие критерии:

  • Отсутствует заранее созданное графическое представление модели объекта.
  • Вы начали писать реализации для одиночных методов, не создав заглушек для остальных компонентов модели.
  • Обсуждая свою работу, вы не используете названий шаблонов проектирования, а вместо этого говорите о конкретной реализации.

Хорошая привычка: согласованное добавление объектов в составе шаблонов

В общем случае вы мыслите в шаблонах, если соблюдаются следующие критерии:

  • Вы заранее смоделировали классы и их взаимодействия.
  • Вы стереотипируете классы согласно их шаблонам.
  • Вы используете такие названия шаблонов, как Factory, Singleton и Facade.
  • Вы сначала создали заглушки для значительной части компонентов модели и только после этого приступили к реализации.

Заключение

Выработка хороших объектно-ориентированных привычек при программировании на PHP помогает создавать более устойчивые, легко сопровождаемые и проще расширяемые приложения. Итак, не забывайте о следующих привычках:

  • Будьте скромны.
  • Будьте хорошим соседом.
  • Не смотрите на Медузу Горгону.
  • Применяйте самое слабое связывание.
  • Обеспечьте высокое сцепление.
  • Поддерживайте семейственность.
  • Мыслите шаблонами.

Когда вы сформируете у себя эти привычки и станете руководствоваться ими в своей повседневной деятельности, вы, вероятно, будете приятно удивлены изменениями в качестве ваших приложений.

Ресурсы

Научиться

  • Оригинал статьи: Build seven good object-oriented habits in PHP (EN).
  • Прочитайте в Википедии статью Модульное программирование (EN).
  • Углубите свои знания об объектно-ориентированном проектировании – прочитайте книгу Бертрана Мейера (Bertrand Meyer) Object-Oriented Software Construction (EN) (Объектно-ориентированное конструирование программного обеспечения).
  • Посетите на официальном Web-сайте с руководствами по PHP раздел под названием: Классы и объекты (EN).
  • Прочитайте две статьи о шаблонах проектирования в PHP: "Five common PHP design patterns" (EN) (Пять типовых шаблонов проектирования в PHP) и "Five more PHP design patterns" (EN) (Еще пять шаблонов проектирования в PHP).
  • PHP.net (EN) центральный ресурс для PHP-разработчиков.
  • Ознакомьтесь со "списком рекомендованной литературы по PHP." (EN)
  • Просмотрите все материалы по PHP на ресурсе developerWorks.
  • Для углубления навыков по PHP рекомендуем посетить раздел ресурса IBM developerWorks под названием Ресурсы проекта PHP (EN).
  • Прослушайте интересные интервью и дискуссии для разработчиков ПО: подкасты ресурса developerWorks (EN).
  • Используете базу данных с PHP? Ознакомьтесь с продуктом Zend Core for IBM (EN) – простой в установке и применении, готовой к использованию средой для разработки и эксплуатации кода на языке PHP, поддерживающей IBM DB2 V9.
  • Следите за техническими событиями и Web-трансляциями на ресурсе developerWorks (EN).
  • Своевременно узнавайте о намечаемых конференциях, выставках, Web-трансляциях и других событиях (EN) во всем мире, представляющих интерес для разработчиков продуктов IBM с открытым кодом.
  • Посетите раздел Web-сайта developerWorks, посвященный продуктам с открытым кодом (EN) – к вашим услугам обширный ассортимент справочной информации, инструментов и обновлений, который поможет осуществлять разработки на основе технологий с открытым кодом и использовать их вместе с продуктами IBM.
  • Наблюдайте и изучайте технологии IBM и технологии с открытым кодом, а также функции новых продуктов бесплатно: демонстрации «по запросу» на ресурсе developerWorks (EN).

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source
ArticleID=398670
ArticleTitle=Семь хороших объектно-ориентированных привычек при программировании на PHP
publish-date=06222009