3. S.O.L.I.D. - принципы
1. Принцип единственности ответственности (SRP: Single Responsibility
Principle)
2. Принцип открытости/закрытости (OCP: Open/Closed Principle)
3. Принцип подстановки Лисков (LSP: Liskov Substitution Principle)
4. Принцип разделения интерфейса (ISP: Interface Segregation Principle)
5. Принцип инверсии зависимостей (DIP: Dependency Inversion Principle)
4. SRP - Принцип единственности ответственности
Формулировка:
Не должно быть больше одной причины для изменения класса
Каждый объект должен иметь одну обязанность и эта обязанность должна
быть полностью инкапсулирована в класс.
5. SRP - Принцип единственности ответственности
class Order
{
public function calculateTotalSum() {/*...*/}
public function getItems() {/*...*/}
public function getItemCount() {/*...*/}
public function addItem($item) {/*...*/}
public function deleteItem($item) {/*...*/}
public function printOrder() {/*...*/}
public function showOrder() {/*...*/}
public function load() {/*...*/}
public function save() {/*...*/}
public function update() {/*...*/}
public function delete() {/*...*/}
}
6. SRP - Принцип единственности ответственности
3 различный типов задач (3-и причины для изменения одного класса):
• работа с самим заказом
• отображение заказа
• работа с хранилищем данных
Решение:
Сделать отдельные классы. Чтобы каждый класс занимается своей
конкретной задачей и для каждого класса была только 1 причина для его
изменения.
7. SRP - Принцип единственности ответственности
class Order {
public function calculateTotalSum() {/*...*/}
public function getItems() {/*...*/}
public function getItemCount() {/*...*/}
public function addItem($item) {/*...*/}
public function deleteItem($item) {/*...*/}
}
class OrderRepository {
public function load($orderID){/*...*/}
public function save($order){/*...*/}
public function update($order){/*...*/}
public function delete($order){/*...*/}
}
class OrderViewer {
public function printOrder($order){/*...*/}
public function showOrder($order){/*...*/}
}
8. SRP - Принцип единственности ответственности
Проблема: Задача валидации данных
Первое решение:
class Product {
public function isValid() {
return $this->price > 0;
}
}
Решение: отдать ответственность за валидацию данных продукта другому
объекту. Причем надо сделать так, чтобы сам объект продукта не зависел
от конкретной реализации его валидатора.
$validator = $this->get('validator');
$errors = $validator->validate($product);
9. OCP - Принцип открытости/закрытости
Формулировка:
• программные сущности (классы, модули, функции и т.д.) должны быть
открыты для расширения, но закрыты для изменения
Принцип открытости/закрытость дает понимание того, как оставаться
достаточно гибкими в условиях постоянно меняющихся требований.
Все классы, функции и т.д. должны проектироваться так, чтобы для
изменения их поведения, нам не нужно было изменять их исходный
код.
10. OCP - Принцип открытости/закрытости
class Logger {
public function Log($logText) { /* Save to file */ }
}
class SmtpMailer {
private $logger;
public function __construct() {
$this->logger = new Logger();
}
public function sendMessage($message) {
// Send
// Save to log
$this->logger->Log($message);
}
}
11. OCP - Принцип открытости/закрытости
class DbLogger {
public function Log($logText) { /* Save to Db */ }
}
class SmtpMailer {
private $logger;
public function __construct() {
$this->logger = new DbLogger();
}
public function sendMessage($message) {
// Send
// Save to log
$this->logger->Log($message);
}
}
12. OCP - Принцип открытости/закрытости
interface LoggerInterface {
public function Log($logText);
}
class Logger implements LoggerInterface {
public function Log($logText) { /* Save to file */ }
}
class DbLogger implements LoggerInterface {
public function Log($logText) { /* Save to file */ }
}
13. OCP - Принцип открытости/закрытости
class SmtpMailer {
private $logger;
public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
}
public function sendMessage($message) {
// Send
// Save to log
$this->logger->Log($message);
}
}
14. OCP - Принцип открытости/закрытости
Конкретизируя классы методом instanceof мы должны сразу понять, что наш
код начал "попахивать":
abstract class BaseEntity { }
class AcountEntity extends BaseEntity { }
class RoleEntity extends BaseEntity { }
class Repository {
public function save(BaseEntity $entiry) {
if ($entiry instanceof AcountEntity) {
// ...
} else if ($entiry instanceof RoleEntity) {
// ...
}
}
}
15. OCP - Принцип открытости/закрытости
1. "Открыт для расширения":
поведение может быть расширено
путем добавления новых объектов,
реализующих новые аспекты
поведения;
2. "Закрыт для модификации": в
результате расширения поведения
исходный код объекта не может быть
изменен.
16. LSP - Принцип замещения Лисков
Формулировка №1: eсли для каждого объекта o1 типа S существует объект
o2 типа T, который для всех программ P определен в терминах T, то
поведение P не изменится, если o1 заменить на o2 при условии, что S
является подтипом T.
17. LSP - Принцип замещения Лисков
Формулировка №1: eсли для каждого объекта o1 типа S существует объект
o2 типа T, который для всех программ P определен в терминах T, то
поведение P не изменится, если o1 заменить на o2 при условии, что S
является подтипом T.
Формулировка №2: подтипы должны быть заменяемы базовыми типами.
18. LSP - Принцип замещения Лисков
Поведение наследуемых классов не должно противоречить поведению,
заданному базовым классом, то есть поведение наследуемых классов
должно быть ожидаемым для кода, использующего переменную базового
типа.
interface CollectionInterface {
public function get($index);
public function count();
}
class MyCollection implements CollectionInterface {
public function get($index) { }
public function count() {}
}
$myCollection = new MyCollection();
if (1 == $myCollection->count()) {
$firstItem = $collection->get(0); // Exception, null
}
19. LSP - Принцип замещения Лисков
• Следовать этому принципу очень важно при проектировании новых типов
с использованием наследования.
• Этот принцип предупреждает разработчика о том, что изменение
унаследованного производным типом поведения очень рискованно.
Пример рассмотрен в книге Роберта Мартина «Быстрая разработка
программ» в разделе «Принцип подстановки Лискоу. Реальный пример»
20.
21. ISP - Принцип разделения интерфейса
Формулировка: клиенты не должны зависеть от методов, которые они не
используют
Как и при использовании других принципов проектирования классов мы
пытаемся избавиться от ненужных зависимостей в коде, сделать код легко
читаемым и легко изменяемым.
22. ISP - Принцип разделения интерфейса
interface IItem {
public function applyDiscount($discount);
public function applyPromocode($promocode);
public function setColor($color);
public function setSize($size);
public function setCondition($condition);
public function setPrice($price);
}
23. ISP - Принцип разделения интерфейса
interface IItem {
public function setCondition($condition);
public function setPrice($price);
}
interface IClothes {
public function setColor($color);
public function setSize($size);
public function setMaterial($material);
}
interface IDiscountable {
public function applyDiscount($discount);
public function applyPromocode($promocode);
}
24. ISP - Принцип разделения интерфейса
class Book implemets IItem, IDiscountable {
public function setCondition($condition){/*...*/}
public function setPrice($price){/*...*/}
public function applyDiscount($discount){/*...*/}
public function applyPromocode($promocode){/*...*/}
}
class KidsClothes implemets IItem, IClothes {
public function setCondition($condition){/*...*/}
public function setPrice($price){/*...*/}
public function setColor($color){/*...*/}
public function setSize($size){/*...*/}
public function setMaterial($material){/*...*/}
}
25. DIP - Принцип инверсии зависимости
Формулировка:
• Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба
должны зависеть от абстракции.
• Абстракции не должны зависеть от деталей. Детали должны зависеть от
абстракций.
26. DIP - Принцип инверсии зависимости
class OrderProcess {
public function CalculeteTotal(Order $order) {
$itemTotal = $order->getItemTotal();
$discountCalculator = new DiscountCalculator();
$discountAmount = $discountCalculator->calculateDiscount($order);
$taxAmount = 0;
if($order->getCountry() == "US") {
$taxAmount = $this->getTaxAmount($order);
} elseif ($order->getCountry() == "UK") {
$taxAmount = $this->getVatAmount($order);
}
return $itemTotal - $discountAmount + $taxAmount;
}
private function getTaxAmount(Order $order) { }
private function getVatAmount(Order $order) { }
}
27. DIP - Принцип инверсии зависимости
Причина, по которой проекты "стареют", заключается в том, что у
разработчиков нет возможности безболезненно менять код каких-то
компонентов без боязни нарушить работу других.
Дизайн таких систем можно охарактеризовать следующими признаками:
• Жесткость - изменение одной части кода затрагивает слишком много
других частей;
• Хрупкость - даже незначительное изменение в коде может привести к
совершенно неожиданным проблемам;
• Неподвижность - никакая из частей приложения не может быть легко
выделена и повторно использована.
28. DIP - Принцип инверсии зависимости
Перечислим все обязанности, которые выполняет класс OrderProcessor:
• Знает, как вычислить сумму заказа;
• Знает, как и каким калькулятором вычислить сумму скидки;
• Знает, что означают коды стран;
• Знает, каким образом вычислить сумму налога для той или иной
страны;
• Знает формулу, по которой из всех слагаемых вычисляется стоимость
заказа.
OrderProcess DiscountCalculator
29. DIP - Принцип инверсии зависимости
interface DiscountCalculatorInterface {
public function calculateDiscount(Order $order);
}
class DiscountCalculator implements DiscountCalculatorInterface {
public function calculateDiscount(Order $order) { }
}
interface TaxStrategyInterface {
public function getTaxAmount(Order $order);
}
class USTaxStarategy implements TaxStrategyInterface {
public function getTaxAmount(Order $order) { }
}
class UKTaxStarategy implements TaxStrategyInterface {
public function getTaxAmount(Order $order) { }
}`
30. DIP - Принцип инверсии зависимости
class OrderProcess {
/** @var DiscountCalculatorInterface */
private $discountCalculator;
/** @var TaxStrategyInterface */
private $taxStarategy;
public function __construct(DiscountCalculatorInterface $discountCalculator,
TaxStrategyInterface $taxStarategy) {
$this->discountCalculator = $discountCalculator;
$this->taxStarategy = $taxStarategy;
}
public function CalculeteTotal(Order $order) {
$itemTotal = $order->getItemTotal();
$discountAmount = $this->discountCalculator->calculateDiscount($order);
$taxAmount = $this->taxStarategy->getTaxAmount($order);
return $itemTotal - $discountAmount + $taxAmount;
}
}
31. DIP - Принцип инверсии зависимости
OrderProcess
DiscountCalculator
DiscountCalculatorInterface TaxStrategyInterface
USTaxStarategy UKTaxStarategy
32. DIP - Принцип инверсии зависимости
Принцип обращения зависимости - это очень мощный
инструмент, который в сочетании с другими SOLID-
принципами позволяет разрабатывать дизайн систем
так же легко, как если бы он собирался из
конструктора LEGO.