
![]() |
Здравствуйте Гость ( Вход | Регистрация ) |
|
|
|
![]() ![]() ![]() |
![]() |
|
![]() ![]() орангутанг ![]() ![]() ![]() ![]() ![]() ![]() Профиль Группа: Форумчанин ![]() Сообщений: 2120 Пользователь №: 36605 На форуме: Карма: 115 ![]() |
1. Вступление
Сегодня мы поговорим об ООП, в частности о принципах SOLID, управлению зависимостями в php-коде и Ioc контейнерах (Inversion of Control). Аббревиатура SOLID является самой известной (после ООП), она говорит нам о 5 принципах “хорошего дизайна ПО”. Автор аббревиатуры SOLID - Роберт Мартин, он придумал саму аббревиатуру и описал 5 принципов . На самом деле он описал больше, но звучных буквосочетаний не придумал, поэтому стоит почитать первоисточник. Заметьте, что Мартин именно описал принципы, он не является их автором. Зачем эти правила? Они помогают построить архитектуру приложения, которое со временем, возможно, будет проще (дешевле) поддерживать и развивать. Помогают писать повторно используемый код. 2. Принципы SOLID 1. Принцип единой ответственности (Single Responsibility Principle) 2. Принцип открытия/закрытия (Open/Closed Principle) 3. Принцип подстановки Лискоу (Liskov Substitution Principle) 4. Принцип отделения интерфейса (Interface Segregation Principle) 5. Принцип инверсии зависимостей (Dependency Inversion Principle) с которым как раз и тесно связана вторая часть лекции управление зависимостями в php-коде и Ioc контейнеры (Inversion of Control). В данной статье будет рассмотрен c примерами несколько первых 2.1. Single Responsibility Principle - Принцип единой ответственности Если сказать просто, то принцип SRP можно расшифровать как: На каждый объект должна быть возложена одна единственная обязанность. Или не должно быть больше одной причины для изменения класса. Почему? Потому что это ведет к хрупкости дизайна (пишем один функционал - ”отваливается” другой). То есть, если часть некоторого модуля не имеет никаких ссылок на другую часть этого модуля, то эти части можно разделить на разные модули. Если модули могут меняться независимо, то тоже разделить нужно. Начинать стоит с простого: если можно отделить – надо отделить. Необходимо чтобы внутри одного модуля весь функционал был связан между собой (высокая связанность, high cohesion, такое словосочетание вы наверное слышали). Принцип работает только в одну сторону: если подмножество A некоторого модуля не имеет ссылок на подмножество B, то это не значит что B не имеет ссылок на A, причем скорее всего именно B будет ссылаться на A. Следуя данному принципу весь код будет распадаться на множество маленьких модулей, многие из которых выродятся до одной функции. Это нормально, даже хорошо. Функции потом можно группировать в модули по логической связности, добиваясь все того же high cohesion. Модули будут зависеть друг от друга, они будут выстраиваться в ориентированный граф. Расположив зависимости сверху вниз можно условно разделить модули на верхне- и нижне- уровневые. На само “пространство”, в котором мы пытаемся упорядочить модули, многомерно. Придумать одно отношение порядка для всего этого пространства невозможно. Но для двух модулей, между которыми есть путь, можно сказать какой из них верхнеуровневый, а какой нижнеуровневый. Зависимость между модулями может быть: • Ссылочной, когда модуль A непосредственно обращается к модулю B, его функциям и данным. • Наследованием, когда модуль A является частным случаем B. • Зависимостью по состоянию, когда два модуля оперируют одним внешним состоянием (глобальные переменные, файлы, БД) и влияют на работу друг друга. Это плохая зависимость, от нее надо избавляться. • Зависимостью по времени. Когда для работы одного модуля требуется вызов функций другого модуля в нужные моменты. Это самый плохой вид зависимости, он него надо избавляться однозначно всеми возможными способами. Пример: class Person Следуя приведенному принципу видно, что этот класс можно разделить. Сейчас он отвечает за установку, получение данных, а также за какой-то их вывод. Лучшей структурой, опять же руководствуясь принципом Single Responsibility, будет: class Person Здесь мы выделили вывод данных в отдельный класс и организовали класс контроллер. Пример может показаться надуманным, но второй вариант кода в действительности более устойчивый к изменениям и лучше подходит для повторного использования. Person класс просто хранит данные (в него легко при надобности можно будет добавить новые данные, что не затронет остальной функционал), класс NameView только для вывода (а в дальнейшем выводить может понадобиться разными вариантами) и класс Controller реагирует на запросы (которые опять же здесь могут добавляться, изменяться, не трогая другие классы), получая все необходимые данные и выполняя что необходимо через другие классы и их методы. 2.2. Open/Closed Principle - Принцип открытия/закрытия Этот принцип можно расшифровать, как программные сущности должны быть открыты для расширения, но закрыты для модификации. Возможно, Вы слышали другую его формулировку: "Не хакай ядро". На практике этот принцип обеспечивается путем правильного написания классов и чрезвычайно избирательного использования ключевых слов "private" и "final", что позволяет в дальнейшем расширить базовый класс, не внося изменений в него. Пример: class Person В этом примере добавился класс Employee вроде логично расширяющий класс Person. Но в классе NameView добавились проверки на тип, а что если нам понадобиться добавить новый тип, например студент, расширяющий класс Person . Добавляя его, нам придется изменять и класс NameView, что не есть хорошо. Следуя описанному принципу эту проблему можно решить двумя способами. Либо добавить класс PersonWelcomeMessage, который генерирует приветствие, и расширить его в EmployeeWelcomeMessage и StudentWelcomeMessage для каждого типа, но это несколько избыточно в данном примере. Либо можно добавить в класс Person функцию getTitle, которая и определит что это за персона, изменяясь в каждом наследнике класса Person. В этом случае при добавлении нового класса, расширяющего Person, будет необходимо работать только с этим новым классом, не изменяя остальные. class Person Еще пример: class Logger { Чем плох этот код? Изменение требований: лог надо хранить в БД. class DBLogger { А если, снова какие изменения? Готовимся к борьбе с изменениями требований (а не к борьбе с менеджерами). interface ILogger { Готов выслушать вопросы. |
![]() |
|||
![]() Здесь живет ![]() ![]() ![]() ![]() ![]() ![]() Профиль Группа: Форумчанин ![]() Сообщений: 753 Пользователь №: 32032 На форуме: Карма: 18 ![]() |
И как понять, куда записывается лог, в файл или в базу? если название метода одинаково |
||
![]() |
[x]
Дата
|
![]() Здесь живет ![]() ![]() ![]() ![]() ![]() ![]() Профиль Группа: Форумчанин ![]() Сообщений: 753 Пользователь №: 32032 На форуме: Карма: 18 ![]() |
+1
Супер, вот реализовал интерфейс. interface ILogger { |
![]() |
|||
![]() Здесь живет ![]() ![]() ![]() ![]() ![]() ![]() Профиль Журнал Группа: ★ЛжеЭксперт★ ![]() Сообщений: 26774 Пользователь №: 21350 На форуме: Карма: 756 ![]() |
ай-ай-ай Для ТС - спасиб. удобно рассматривать описание какой либо логики "на пальцах" - усваивается лучше. в принципе так и должна быть спроектирована и логика и реализация при ооп подходе. Жду продолжения. -------------------- HTML, CSS (Bootstrap), JS(JQuery, ExtJS), PHP, MySQL, MSSql, Posgres, (TSql, BI OLAP, MDX), Mongo, Git, SVN, CodeIgnater, Symfony, Yii 2, JiRA, Redmine, Bitbucket, Composer, Rabbit MQ, Amazon (SQS, S3, Transcribe), Docker
|
||
![]() |
|||
![]() Здесь живет ![]() ![]() ![]() ![]() ![]() ![]() Профиль Журнал Группа: ★ЛжеЭксперт★ ![]() Сообщений: 26774 Пользователь №: 21350 На форуме: Карма: 756 ![]() |
оффтоп кстати, хочу сообщить на будущее что конструкции вида
на моей практике некоторые хостеры не понимали, но понимали if($file === true) или if(!$file) -------------------- HTML, CSS (Bootstrap), JS(JQuery, ExtJS), PHP, MySQL, MSSql, Posgres, (TSql, BI OLAP, MDX), Mongo, Git, SVN, CodeIgnater, Symfony, Yii 2, JiRA, Redmine, Bitbucket, Composer, Rabbit MQ, Amazon (SQS, S3, Transcribe), Docker
|
||
![]() |
|||
![]() ![]() Здесь живет ![]() ![]() ![]() ![]() ![]() ![]() Профиль Группа: Эксперт ![]() Сообщений: 9631 Пользователь №: 26630 На форуме: Карма: 664 ![]() |
ты наверно что то путаешь ![]() |
||
![]() |
|
![]() Здесь живет ![]() ![]() ![]() ![]() ![]() ![]() Профиль Журнал Группа: ★ЛжеЭксперт★ ![]() Сообщений: 26774 Пользователь №: 21350 На форуме: Карма: 756 ![]() |
да нет. точно помню что не реагировал. пришлось идти от обратного !true
-------------------- HTML, CSS (Bootstrap), JS(JQuery, ExtJS), PHP, MySQL, MSSql, Posgres, (TSql, BI OLAP, MDX), Mongo, Git, SVN, CodeIgnater, Symfony, Yii 2, JiRA, Redmine, Bitbucket, Composer, Rabbit MQ, Amazon (SQS, S3, Transcribe), Docker
|
![]() |
|||
![]() ![]() Здесь живет ![]() ![]() ![]() ![]() ![]() ![]() Профиль Группа: Эксперт ![]() Сообщений: 9631 Пользователь №: 26630 На форуме: Карма: 664 ![]() |
Такого быть не может, причина точно была в чем то другом. ![]() |
||
![]() |
[x]
Дата
|
![]() ![]() Здесь живет ![]() ![]() ![]() ![]() ![]() ![]() Профиль Группа: Форумчанин ![]() Сообщений: 894 Пользователь №: 30580 На форуме: Карма: 43 ![]() |
Я примерно вот такой говнокод юзаю:
<?php Таким образом я могу пройтись по всему проекту с Log и увидить где у меня логи идут, а когда DBLogger, FileLogger, AssLogir, ShitLager ну есть кое какие проблемы, а второе пихать в конструктор и таскать с собой это постоянно утомительно, можно вообще даже тип не объявлять задать стандартный какой будет и все и похрену дым, а где нужно прописал тип, но могут быть проблемы кое какие но об этом я смолчу :) |
![]() |
۩
Дата
|
![]() ![]() орангутанг ![]() ![]() ![]() ![]() ![]() ![]() Профиль Группа: Форумчанин ![]() Сообщений: 2120 Пользователь №: 36605 На форуме: Карма: 115 ![]() |
Ну пихать в конструктор обоснованно, причины будут в одной из дальнейших статей, про зависимости в коде.
PS в твоем примере is_subclass_of($class_name, ILogger) можно заменить на ($class_name instanceof ILogger) принимает как кавычки так и без для 'ILogger' а по хорошему использовать типизацию, тогда не нужно будет делать и проверок public static function type(ILogger $class_name) это будет правильнее |
![]() |
|||
![]() Здесь живет ![]() ![]() ![]() ![]() ![]() ![]() Профиль Группа: Форумчанин ![]() Сообщений: 753 Пользователь №: 32032 На форуме: Карма: 18 ![]() |
Игорь_Vasinsky, спс, по поводу if($file) или if(!$file), исправил, вспомнил кое-что, т.к. не моё, процитирую автора. Как говорят чтобы запомнить.
Теперь мне понятно, почему это нужно было хорошенько запомнить. Cорри за оффтоп. |
||
![]() |
|||
![]() ![]() Новичок ![]() ![]() ![]() ![]() ![]() ![]() Профиль Журнал Группа: Форумчанин ![]() Сообщений: 1609 Пользователь №: 22453 На форуме: Карма: 108 ![]() |
Попытка узнать эту информацию является первым нарушением правила "Single Responsibility". В функции sale понадобилось произвести log, какого подтипа этот лог, и имеется ли он вообще, тебя интересовать не должно. В том и прелесть Single Responsibility, что ты занимаешься только одной задачей, а не пытаешься решить все проблемы сразу ![]() -------------------- |
||
![]() |
|||||||
![]() Здесь живет ![]() ![]() ![]() ![]() ![]() ![]() Профиль Группа: Форумчанин ![]() Сообщений: 753 Пользователь №: 32032 На форуме: Карма: 18 ![]() |
) правильное написание классов - должно быть неотъемлемой частью всех принципов SOLID. Или нет? Пример Open/Closed Principle приведен как-то непонятно, всё в кучу намешалось. В поисках более подробной информации нашел очень похожий материал. http://blogerator.ru/page/oop-tverdye-obek...ncipy-solid-php
Следуя описанному принципу нужно что-то открыть (public), а что-то закрыть(private, protected, final), или наоборот, а не расширять и добавлять. В общем, пример не совсем в тему. |
||||||
![]() |
۩
Дата
|
![]() ![]() орангутанг ![]() ![]() ![]() ![]() ![]() ![]() Профиль Группа: Форумчанин ![]() Сообщений: 2120 Пользователь №: 36605 На форуме: Карма: 115 ![]() |
Пример как раз таки конкретный, класс закрыт для изменения и открыт для дополнения, причина изменения класса должна быть очень веской, либо это рефакторинг, то есть условие такое что мы пишем новый код, а не дополняем существуюший. и тут уже на встречу нам приходит IoC и тд
то есть вместо кучи If и case мы заменяем на IоС контейнер и делаем кучу нужных классов, как пример логгер, мы не пишем туда if , а пишем новый логгер и его используем, а старый лежит или используется в другом месте. |
![]() |
|||
![]() Здесь живет ![]() ![]() ![]() ![]() ![]() ![]() Профиль Группа: Форумчанин ![]() Сообщений: 753 Пользователь №: 32032 На форуме: Карма: 18 ![]() |
В Open/Closed Principle есть 2 примера не связанные между собой. О каком классе идет речь? В первом примере не нахожу ничего закрытого для изменений. Во втором есть одно приватное свойство, оставшееся как-то без внимания. |
||
![]() |
![]() ![]() ![]() |