SOLID
-
Upload
alexander-nemanov -
Category
Technology
-
view
314 -
download
4
description
Transcript of SOLID
S.O.L.I.D.Принципыобъектного
проектирования классов
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)
SRP - Принцип единственности ответственности
Формулировка:Не должно быть больше одной причины для изменения класса
Каждый объект должен иметь одну обязанность и эта обязанность должна быть полностью инкапсулирована в класс.
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() {/*...*/}}
SRP - Принцип единственности ответственности
3 различный типов задач (3-и причины для изменения одного класса):
• работа с самим заказом• отображение заказа• работа с хранилищем данных
Решение:Сделать отдельные классы. Чтобы каждый класс занимается своей конкретной задачей и для каждого класса была только 1 причина для его изменения.
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){/*...*/} }
SRP - Принцип единственности ответственности
Проблема: Задача валидации данныхПервое решение:
class Product { public function isValid() { return $this->price > 0; }}
Решение: отдать ответственность за валидацию данных продукта другому объекту. Причем надо сделать так, чтобы сам объект продукта не зависел от конкретной реализации его валидатора.
$validator = $this->get('validator');$errors = $validator->validate($product);
OCP - Принцип открытости/закрытости
Формулировка:• программные сущности (классы, модули, функции и т.д.) должны быть
открыты для расширения, но закрыты для изменения
Принцип открытости/закрытость дает понимание того, как оставаться достаточно гибкими в условиях постоянно меняющихся требований.
Все классы, функции и т.д. должны проектироваться так, чтобы для изменения их поведения, нам не нужно было изменять их исходный код.
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); }}
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); }}
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 */ }}
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); }}
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) { // ... } }}
OCP - Принцип открытости/закрытости
1. "Открыт для расширения": поведение может быть расширено путем добавления новых объектов, реализующих новые аспекты поведения;
2. "Закрыт для модификации": в результате расширения поведения исходный код объекта не может быть изменен.
LSP - Принцип замещения Лисков
Формулировка №1: eсли для каждого объекта o1 типа S существует объект o2 типа T, который для всех программ P определен в терминах T, то поведение P не изменится, если o1 заменить на o2 при условии, что S является подтипом T.
LSP - Принцип замещения Лисков
Формулировка №1: eсли для каждого объекта o1 типа S существует объект o2 типа T, который для всех программ P определен в терминах T, то поведение P не изменится, если o1 заменить на o2 при условии, что S является подтипом T.
Формулировка №2: подтипы должны быть заменяемы базовыми типами.
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}
LSP - Принцип замещения Лисков• Следовать этому принципу очень важно при проектировании новых типов
с использованием наследования.• Этот принцип предупреждает разработчика о том, что изменение
унаследованного производным типом поведения очень рискованно.
Пример рассмотрен в книге Роберта Мартина «Быстрая разработка программ» в разделе «Принцип подстановки Лискоу. Реальный пример»
ISP - Принцип разделения интерфейса
Формулировка: клиенты не должны зависеть от методов, которые они не используют
Как и при использовании других принципов проектирования классов мы пытаемся избавиться от ненужных зависимостей в коде, сделать код легко читаемым и легко изменяемым.
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);}
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);}
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){/*...*/}}
DIP - Принцип инверсии зависимости
Формулировка:
• Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракции.
• Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
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) { }}
DIP - Принцип инверсии зависимости
Причина, по которой проекты "стареют", заключается в том, что у разработчиков нет возможности безболезненно менять код каких-то компонентов без боязни нарушить работу других.
Дизайн таких систем можно охарактеризовать следующими признаками:• Жесткость - изменение одной части кода затрагивает слишком много
других частей;
• Хрупкость - даже незначительное изменение в коде может привести к совершенно неожиданным проблемам;
• Неподвижность - никакая из частей приложения не может быть легко выделена и повторно использована.
DIP - Принцип инверсии зависимости
Перечислим все обязанности, которые выполняет класс OrderProcessor:
• Знает, как вычислить сумму заказа;• Знает, как и каким калькулятором вычислить сумму скидки;• Знает, что означают коды стран;• Знает, каким образом вычислить сумму налога для той или иной
страны;• Знает формулу, по которой из всех слагаемых вычисляется стоимость
заказа.
OrderProcess DiscountCalculator
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) { }}`
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; }}
DIP - Принцип инверсии зависимости
OrderProcess
DiscountCalculator
DiscountCalculatorInterface TaxStrategyInterface
USTaxStarategy UKTaxStarategy
DIP - Принцип инверсии зависимости
Принцип обращения зависимости - это очень мощный инструмент, который в сочетании с другими SOLID-принципами позволяет разрабатывать дизайн систем так же легко, как если бы он собирался из конструктора LEGO.
?