[ Поиск ] - [ Пользователи ] - [ Календарь ]
Полная Версия: SOLID что такие и с чем едят
Страницы: 1, 2
bestxp
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
{
public function getName()...
public function setName()...
public function echoName()...
}

Следуя приведенному принципу видно, что этот класс можно разделить. Сейчас он отвечает за установку, получение данных, а также за какой-то их вывод. Лучшей структурой, опять же руководствуясь принципом Single Responsibility, будет:
class Person 
{
public function getName()...
public function setName()...
}

class NameView
{
public function doOutput($name)...
}

class Controller
{
public function personAction()
{
// Get a person from some data source
$person = ...;
$view = new NameView();
$view->doOutput($person->getName());
}
}

Здесь мы выделили вывод данных в отдельный класс и организовали класс контроллер. Пример может показаться надуманным, но второй вариант кода в действительности более устойчивый к изменениям и лучше подходит для повторного использования. Person класс просто хранит данные (в него легко при надобности можно будет добавить новые данные, что не затронет остальной функционал), класс NameView только для вывода (а в дальнейшем выводить может понадобиться разными вариантами) и класс Controller реагирует на запросы (которые опять же здесь могут добавляться, изменяться, не трогая другие классы), получая все необходимые данные и выполняя что необходимо через другие классы и их методы.
2.2. Open/Closed Principle - Принцип открытия/закрытия
Этот принцип можно расшифровать, как программные сущности должны быть открыты для расширения, но закрыты для модификации. Возможно, Вы слышали другую его формулировку: "Не хакай ядро". На практике этот принцип обеспечивается путем правильного написания классов и чрезвычайно избирательного использования ключевых слов "private" и "final", что позволяет в дальнейшем расширить базовый класс, не внося изменений в него.
Пример:
class Person 
{
public function getName()...
public function setName()...
}

class Employee extends Person
{
...
}

class NameView
{
public function doOutput(Person $person)
{
if (Person instanceof Employee)
{
echo ‘Hi employee ‘, $person->getName();
}
else
{
Echo ‘Hi person ‘, $person->getName();
}
}
}


class Controller
{
public function personAction()
{
// Get a person from some data source
$person = ...;
$view = new NameView();
$view->doOutput($person);
}
}

В этом примере добавился класс Employee вроде логично расширяющий класс Person. Но в классе NameView добавились проверки на тип, а что если нам понадобиться добавить новый тип, например студент, расширяющий класс Person . Добавляя его, нам придется изменять и класс NameView, что не есть хорошо. Следуя описанному принципу эту проблему можно решить двумя способами. Либо добавить класс PersonWelcomeMessage, который генерирует приветствие, и расширить его в EmployeeWelcomeMessage и StudentWelcomeMessage для каждого типа, но это несколько избыточно в данном примере. Либо можно добавить в класс Person функцию getTitle, которая и определит что это за персона, изменяясь в каждом наследнике класса Person. В этом случае при добавлении нового класса, расширяющего Person, будет необходимо работать только с этим новым классом, не изменяя остальные.
class Person 
{
...
public function getName()...
public function setName()...

public function getTitle()
{
return ‘person’;
}
}


class Employee extends Person
{
...
public function getTitle()
{
return ‘employee’;
}
}


class NameView
{
public function doOutput(Person $person)
{
echo ‘Hi ‘, $person->getTitle(), ‘ ‘, $person->getName();
}
}


Еще пример:
class Logger { 
public function log($text) { // Сохраняем текст в лог (лог у нас будет храниться в файлах) }
}
class Product {
private $_logger;
public function __construct() { $this->_logger = new Logger(); }
/* Продать товар */
public function sale() {
// … продаем товар
// Записываем дату продажи в лог $this->_logger->log('Sale time: '. time());

}
}

Чем плох этот код? Изменение требований: лог надо хранить в БД.
class DBLogger { 
public function log($text) { // Сохраняем что-то в лог (лог у нас будет храниться в БД) }
}
class Product {
private $_logger;
public function __construct() {
// Меняем класс Product, чтоб поменять логер (помните про SRP?)
$this->_logger = new DBLogger();
}
/* Продать товар*/
public function sale() {
// Продаем товар // ... //
Записываем дату продажи в лог $this->_logger->log('Sale time: '. time());
}
}

А если, снова какие изменения? Готовимся к борьбе с изменениями требований (а не к борьбе с менеджерами).
interface ILogger { 
public function log($text);
}
class Logger implements ILogger {
public function log($text) { // Сохраняем что-то в лог (лог у нас будет храниться в файлах) }
}
class DBLogger implements ILogger {
public function log($text) { // Сохраняем что-то в лог (лог у нас будет храниться в БД) }
}
class Product {
private $_logger;
public function __construct(ILogger $logger) { $this->_logger = $logger; }
/* Продать товар */
public function sale() {
// Продаем товар
// ...
// Записываем дату продажи в лог

$this->_logger->log('Sale time: '. time());
}
}


Готов выслушать вопросы.
Быстрый ответ:

 Графические смайлики |  Показывать подпись
Здесь расположена полная версия этой страницы.
Invision Power Board © 2001-2024 Invision Power Services, Inc.