Просмотров: 17263

Твердые объектно-ориентированные принципы. SOLID


Снова у нас впереди серия постов про зияющие глубины ООП-программирования. На этот раз представляю первый пост из этой новой серии «Твердые объектно-ориентированные принципы».

Это перевод серии одноименных статей от  известного PHP-гуру, входящего в команду разработчиков Core PHP Team — Д. Кита Кейси (D. Keith Casey). И посвящен он важнейшим понятиям объектно-ориентированного дизайна — SOLID-принципам.

PHP oop ооп  объектно-ориентированное программирование

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

Если вы хотите знать как применять их на практике изо дня в день в вашей разработке, вы должны будете погрузиться в SOLID-принципы, которые описывают 5 важных аспектов «хорошего» объектно-ориентированного дизайна.

Принцип единой ответственности

Во-первых, это принцип единой ответственности или SRP (Single Responsibility Principle).

Если коротко, то он означает, что похожие вещи должны держаться вместе и ничего более.

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

PHP oop ооп  объектно-ориентированное программирование SOLID objects объекты принципы

В нашем образцовом открытом проекте web2project есть класс Link со следующими методами:

class CLink extends w2p_Core_BaseObject {
    public function __construct() { }
    public function loadFull(CAppUI $AppUI, $link_id) { }
    public function check() { }
    public function delete(CAppUI $AppUI) { }
    public function store(CAppUI $AppUI) { }
    public function hook_search() { }
}

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

К сожалению, у нас есть еще один метод в данном классе:

public function getProjectTaskLinksByCategory _
(CAppUI $AppUI, $project_id = 0, $task_id = 0, _
$category_id = 0, $search = '') { }

Да, к сожалению, это настоящее имя метода... При взгляде на этот метод, одно лишь его имя уже говорит нам о том, как много всего здесь происходит.

В самом деле, у нас есть идентификаторы трех объектов в качестве входных данных, это означает что мы будем еще взаимодействовать как минимум с тремя другими объектами.

Если эти объекты когда-нибудь изменятся, мы, в конце концов, придем к антипаттерну «shotgun surgery», когда маленькое изменение в одной части кода вызывает небольшие изменения повсюду.

Такой код болезненно поддерживать даже короткое время.

Принцип «Открыто/Закрыто»

Далее, существует еще один принцип: Открыто/Закрыто или OCP (Open/Closed Principle).

Или, в более простых терминах, программные сущности должны быть открыты для расширения, но закрыты для модификации.

Возможно, вы слышали другую его формулировку: «Не хакай ядро». На практике этот принцип обеспечивается путем чрезвычайно избирательного использования ключевых слов «private» и «final».

PHP oop ооп  объектно-ориентированное программирование SOLID objects объекты принципы

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

Если первое, то возможно стоит использовать ключевое слово «protected». Это позволит расширить ваш класс и перегрузить методы в случае необходимости.

Принцип замещения Лискоу

Далее, у нас есть принцип замещения Лискоy или LSP (Liskov Substitution Principle) и, если честно, это наиболее сложная вещь для моего понимания.

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

Примечания переводчика:

Есть также более строгие формулировки этого метода, например:

Формулировка № 1: eсли для каждого объекта o1 типа S существует объект o2 типа T, который для всех программ P определен в терминах T, то поведение P не измениться, если o1 заменить на o2 при условии, что S является подтипом T.

Формулировка № 2: подтипы должны быть заменяемы базовыми типами.

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

PHP oop ооп  объектно-ориентированное программирование SOLID objects объекты принципы

Примечания переводчика:

Поясню на примере. Если для какой-нибудь функции родительского класса справедливо:

* пред-условие: item != null
* пост-условие: count = oldCount + 1

То для дочернего класса можно использовать:

* пред-условие: -
* пост-условие: count = oldCount + 1 && is_changed = true

Более подробно на моем блоге с этим принципом можно ознакомиться вот здесь.

Принцип разделения интерфейса

Следующая концепция это принцип разделения интерфейса или ISP (Interface Segregation Principle).

Формальное определение у него такое: много клиентских специфических интерфейсов лучше, чем один общий интерфейс.

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

PHP oop ооп  объектно-ориентированное программирование SOLID objects объекты принципы

Взгляните на такой (надуманный) пример:

interface w2p_Interfaces_Authenticator {
    public function authenticate($username, $password);
    public function getUserId();
    public function userExists();
    public function changePassword();
}

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

interface w2p_Interfaces_Modules_Setup {
    public function install();
    public function upgrade($old_version);
    public function configure();
    public function uninstall();
    public function changeDisplayOrder();
}

Первые 4 метода имеют смысл в интерфейсе Setup и описывают разумный жизненный цикл модуля. Последний метод имеет мало общего с установкой и скорее относится к уровню представления. Что-то здесь не так... Или, другими словами, у нас получился код «с запашком» (так называемый code smell).

Примечания переводчика:

Другая формулировка принципа: клиенты не должны зависеть от методов, которые они не используют

Принцип инверсии зависимостей

И наконец финальная концепция SOLID — принцип инверсии зависимостей или DIP (Dependency Inversion Principle).

В мире компьютерных наук его формальное определение такое: зависеть от абстракций, а не от деталей.

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

PHP oop ооп  объектно-ориентированное программирование SOLID objects объекты принципы

В web2project часть нашей структуры классов выглядит так:

abstract class w2p_Core_BaseObject {
    public function __construct($table, $key, $module = '') {}
    public function load($oid = null, $strip = true) {}
    public function store($updateNulls = false) {}
    public function check() { }
}
class CProject extends w2p_Core_BaseObject {
    public function __construct() { }
    // load is inherited
    public function store(CAppUI $AppUI) { }
    public function check() { }
}
class CLink extends w2p_Core_BaseObject {
    public function __construct() { }
    // load is inherited
    public function store(CAppUI $AppUI) { }
    public function check() { }
}

Т.к. у нас есть интерфесы, вместо того чтобы сделать наши контроллеры зависимыми от объектов CProject или CLink , мы можем взаимодействовать с w2p_Core_BaseObject например таким образом:

class w2p_Controllers_Base
{
    public function __construct(w2p_Core_BaseObject $object, $delete,
             $prefix, $successPath, $errorPath) { }
}

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

PHP oop ооп  объектно-ориентированное программирование SOLID objects объекты принципы

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

Нельзя просто взять и отметить чекбокс: «теперь мы поддерживаем SOLID».

~

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

Итак, ссылка номер раз — просто обалденный медиа-курс SOLID Principle Episodes, более понятная для большинства ссылка номер два — SOLID: TOP-5 архитектурных принципов + Принцип открытости/закрытости кода и какие трудности могут встать на пути (PHP). И завершающая ссылка номер три для чистых практиков: Шпаргалка по SOLID-принципам с примерами на PHP.

Также для закрепления темы хорошая презентация на русском:

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

Перевод: trish.in, 2012

twitter.com facebook.com vkontakte.ru odnoklassniki.ru mail.ru ya.ru pikabu.ru blogger.com liveinternet.ru livejournal.ru google.com bobrdobr.ru yandex.ru del.icio.us

Подписка на обновления блога → через RSS, на e-mail, через Twitter
Теги: , , , , , , ,
Эта запись опубликована: Среда, 25 января 2012 в рубрике Программирование.

Оставьте комментарий!

Не регистрировать/аноним

Используйте нормальные имена. Ваш комментарий будет опубликован после проверки.

Зарегистрировать/комментатор

Для регистрации укажите свой действующий email и пароль. Связка email-пароль позволяет вам комментировать и редактировать данные в вашем персональном аккаунте, такие как адрес сайта, ник и т.п. (Письмо с активацией придет в ящик, указанный при регистрации)

(обязательно)


⇑ Наверх
⇓ Вниз