SOLID

33
S.O.L.I.D. Принципы объектного проектирования классов

description

S.O.L.I.D. - Принципы объектного проектирования классов

Transcript of SOLID

Page 1: SOLID

S.O.L.I.D.Принципыобъектного

проектирования классов

Page 2: SOLID

Обо мне

Alexander [email protected]

Skype: asoft4webFB: https://

www.facebook.com/alexander.nemanov

Page 3: SOLID

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)

Page 4: SOLID

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

Формулировка:Не должно быть больше одной причины для изменения класса

Каждый объект должен иметь одну обязанность и эта обязанность должна быть полностью инкапсулирована в класс.

Page 5: SOLID

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() {/*...*/}}

Page 6: SOLID

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

3 различный типов задач (3-и причины для изменения одного класса):

• работа с самим заказом• отображение заказа• работа с хранилищем данных

Решение:Сделать отдельные классы. Чтобы каждый класс занимается своей конкретной задачей и для каждого класса была только 1 причина для его изменения.

Page 7: SOLID

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){/*...*/} }

Page 8: SOLID

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

Проблема: Задача валидации данныхПервое решение:

class Product { public function isValid() { return $this->price > 0; }}

Решение: отдать ответственность за валидацию данных продукта другому объекту. Причем надо сделать так, чтобы сам объект продукта не зависел от конкретной реализации его валидатора.

$validator = $this->get('validator');$errors = $validator->validate($product);

Page 9: SOLID

OCP - Принцип открытости/закрытости

Формулировка:• программные сущности (классы, модули, функции и т.д.) должны быть

открыты для расширения, но закрыты для изменения

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

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

Page 10: SOLID

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); }}

Page 11: SOLID

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); }}

Page 12: SOLID

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 */ }}

Page 13: SOLID

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); }}

Page 14: SOLID

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) { // ... } }}

Page 15: SOLID

OCP - Принцип открытости/закрытости

1. "Открыт для расширения": поведение может быть расширено путем добавления новых объектов, реализующих новые аспекты поведения;

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

Page 16: SOLID

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

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

Page 17: SOLID

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

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

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

Page 18: SOLID

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}

Page 19: SOLID

LSP - Принцип замещения Лисков• Следовать этому принципу очень важно при проектировании новых типов

с использованием наследования.• Этот принцип предупреждает разработчика о том, что изменение

унаследованного производным типом поведения очень рискованно.

Пример рассмотрен в книге Роберта Мартина «Быстрая разработка программ» в разделе «Принцип подстановки Лискоу. Реальный пример»

Page 20: SOLID
Page 21: SOLID

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

Формулировка: клиенты не должны зависеть от методов, которые они не используют

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

Page 22: SOLID

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);}

Page 23: SOLID

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);}

Page 24: SOLID

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){/*...*/}}

Page 25: SOLID

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

Формулировка:

• Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракции.

• Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Page 26: SOLID

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) { }}

Page 27: SOLID

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

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

Дизайн таких систем можно охарактеризовать следующими признаками:• Жесткость - изменение одной части кода затрагивает слишком много

других частей;

• Хрупкость - даже незначительное изменение в коде может привести к совершенно неожиданным проблемам;

• Неподвижность - никакая из частей приложения не может быть легко выделена и повторно использована.

Page 28: SOLID

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

Перечислим все обязанности, которые выполняет класс OrderProcessor:

• Знает, как вычислить сумму заказа;• Знает, как и каким калькулятором вычислить сумму скидки;• Знает, что означают коды стран;• Знает, каким образом вычислить сумму налога для той или иной

страны;• Знает формулу, по которой из всех слагаемых вычисляется стоимость

заказа.

OrderProcess DiscountCalculator

Page 29: SOLID

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) { }}`

Page 30: SOLID

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; }}

Page 31: SOLID

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

OrderProcess

DiscountCalculator

DiscountCalculatorInterface TaxStrategyInterface

USTaxStarategy UKTaxStarategy

Page 32: SOLID

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

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

Page 33: SOLID

?