Принципы проектирования S.O.L.I.D

18,823 views

Published on

5 принципов проектировния программных продуктов

Published in: Technology
0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
18,823
On SlideShare
0
From Embeds
0
Number of Embeds
10,865
Actions
Shares
0
Downloads
182
Comments
0
Likes
4
Embeds 0
No embeds

No notes for slide

Принципы проектирования S.O.L.I.D

  1. 1. S.O.L.I.D Расшифруем: S - Single responsibility principle (SRP) O - Open/closed principle (OCP) L - Liskov substitution principle (LSP) I - Interface segregation principle (ISP) D - Dependency inversion principle (DIP)
  2. 2. Зачем эти правила? Помогают построить архитектуру приложения, которое со временем возможно будет проще (дешевле) поддерживать и развивать. Помогают писать повторно используемый код.
  3. 3. Принцип единственности ответственности (SRP) Не должно быть больше одной причины для изменения класса. Почему? Потому что это ведет к хрупкости дизайна (пишем один функционал - ”отваливается” другой).
  4. 4. Высшая раса нарушающая SRP – это God Object Что такое ”плохо”:
  5. 5. Борьба с высшей расой Лучше так:
  6. 6. Принцип открытости/закрытости (OCP) Программные сущности (классы, модули, функции) должны быть открыты для расширения, но закрыты для изменения. Почему? Потому что это позволяет быстро и безболезненно реагировать на изменение бизнес-требований.
  7. 7. 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()); } } Чем плох этот код?
  8. 8. Лог продаж в файлах?! Это же отстой! Изменение требований: лог надо хранить в БД 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()); } }
  9. 9. Готовимся к борьбе с изменениями требований (а не к борьбе с менеджерами) 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()); } }
  10. 10. Принцип подстановки Барбары Лисков (LSP) Поведение наследуемых классов не должно противоречить поведению, заданному базовым классом. Почему? Потому что клиентский код начинает считать производный класс разновидностью базового, и возможно появление кода, явно использующего этот факт.
  11. 11. Байка про утку и батарейки
  12. 12. Ударим кодом по уткам (базовый класс) /* Определим базовую утку */ abstract class Duck { private $_batteryStatus = 100; // Стстус заряда батареек утки (%) private $_steps = 0; // Количество шагов пройденных уткой (шт.) public function getBatteryStatus() { return $this->_batteryStatus; } public function setBatteryStatus($value) { $this->_batteryStatus = $value; } public function getSteps() { return $this->_steps; } public function setSteps($value) { $this->_steps = $value; } // Эти методы абстрактные (разные утки крякают и двигаются по-своему) abstract public function move(); abstract public function quack(); }
  13. 13. Утки на батарейках (Америка, Китай) /* Американская утка на батарейках */ class AmericanDuck extends Duck { public function move() { $this->setSteps($this->getSteps() + 1); // Утка шагает $this->setBatteryStatus($this->getBatteryStatus() - 20); // Батарейки садятся echo "Я прошагала {$this->getSteps()} шагов (заряд батарейки: {$this->getBatteryStatus()}%) n"; } public function quack() { echo "Здравствуйте, я Американская утка! n"; } } /* Китайская утка на батарейках */ class ChinaDuck extends Duck { public function move() { $this->setSteps($this->getSteps() + 1); // Утка шагает $this->setBatteryStatus($this->getBatteryStatus() - 10); // Батарейки садятся (но не так быстро) echo "Я прошагала {$this->getSteps()} шагов (заряд батарейки: {$this->getBatteryStatus()}%) n"; } public function quack() { echo "Здравствуйте, я Китайский утка, я плохо говорить по-русски, но я уметь много шагать! n"; } }
  14. 14. Клиентский код использования уток // Имеем список уток $duckList = array(new AmericanDuck(), new ChinaDuck()); // Просим всех уток по очереди представиться и походить foreach($duckList as $duck) { $duck->quack(); // Представимся while ($duck->getBatteryStatus() > 0) { // Утка должна шагать, пока не сядут батарейки $duck->move(); sleep(1); } echo "n"; }
  15. 15. Появилась возожность юзать живую утку! /* Живая утка. Её особенность в том, что у нее нет батареек и она шагает сколько хочет */ class BrainyDuck extends Duck { public function move() { $this->setSteps($this->getSteps() + 1); // Утка шагает echo "Я прошагала {$this->getSteps()} шагов n"; } public function quack() { echo "Здравствуйте, я живая утка с мозгом! n"; } public function getBatteryStatus() { throw new Exception('Сума сошел? Какие батарейки, я живая!'); } public function setBatteryStatus() { throw new Exception('Сума сошел? Какие батарейки, я живая!'); } }
  16. 16. Клиентский код и Fail исползования уток // Имеем список уток $duckList = array(new AmericanDuck(), new ChinaDuck(), new BrainyDuck()); // Просим всех уток по очереди представиться и походить foreach($duckList as $duck) { $duck->quack(); // Представимся while ($duck->getBatteryStatus() > 0) { // Утка должна шагать, пока не сядут батарейки $duck->move(); sleep(1); } echo "n"; }
  17. 17. Принцип разделения интерфейса (ISP) Клиенты не должны зависеть от методов, которые они не используют. Почему? Если мы определим большой универсальный интерфейс, тогда в наследниках возможно появление множества заглушек, а соответственно, много лишнего кода, который неудобно поддерживать.
  18. 18. Делаем трансформера (пока что все хорошо) interface IMegaTrsansformer { public function transformToCar(); public function transformToShip(); public function transformToPlane(); } class MegaTrsansformer implements IMegaTrsansformer { public function transformToCar() { echo 'Я преобразовался и стал спортивной машиной!'; } public function transformToShip() { echo 'Я преобразовался и стал сверхбыстрым катером!'; } public function transformToPlane() { echo 'Я преобразовался и стал истребителем!'; } }
  19. 19. А теперь нужно сделать менее крутых трансформеров (в каждом из них теперь костыли) class TaxiTrsansformer implements IMegaTrsansformer { public function transformToCar() { echo 'Я преобразовался и стал такси!'; } public function transformToShip() { throw new Exception('Данная трансформация не поддерживается'); } public function transformToPlane() { throw new Exception('Данная трансформация не поддерживается'); } } class StelthTrsansformer implements IMegaTrsansformer { public function transformToCar() { throw new Exception('Данная трансформация не поддерживается'); } public function transformToShip() { throw new Exception('Данная трансформация не поддерживается'); } public function transformToPlane() { echo 'Я преобразовался и стал стелсом!'; } } class IcebreakerTrsansformer implements IMegaTrsansformer { public function transformToCar() { throw new Exception('Данная трансформация не поддерживается'); } public function transformToShip() { echo 'Я преобразовался и стал ледоколом!'; } public function transformToPlane() { throw new Exception('Данная трансформация не поддерживается'); } }
  20. 20. Лучше иметь такой набор интерфейсов interface ICarTrsansformer { public function transformToCar(); } interface IShipTrsansformer { public function transformToShip(); } interface IPlaneTrsansformer { public function transformToPlane(); }
  21. 21. И такой набор классов class MegaTrsansformer implements ICarTrsansformer, IShipTrsansformer, IPlaneTrsansformer { public function transformToCar() { echo 'Я преобразовался и стал спортивной машиной!'; } public function transformToShip() { echo 'Я преобразовался и стал сверхбыстрым катером!'; } public function transformToPlane() { echo 'Я преобразовался и стал истребителем!'; } } class TaxiTrsansformer implements ICarTrsansformer { public function transformToCar() { echo 'Я преобразовался и стал такси!'; } } class StelthTrsansformer implements IPlaneTrsansformer { public function transformToPlane() { echo 'Я преобразовался и стал стелсом!'; } } class IcebreakerTrsansformer implements IShipTrsansformer { public function transformToShip() { echo 'Я преобразовался и стал ледоколом!'; } }
  22. 22. Принцип инверсии зависимостей (DIP) <ul><li>Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций
  23. 23. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций </li></ul>
  24. 24. Анатомия зависимостей (X зависит от Y + циклическая зависимость) Зависимости бывают: - Прямые; - Транзитивные; - Циклические.
  25. 25. Зависимость модуля верхннго уровня от модулей нижнего уровня class siteMapBilder { private $_dataStorage; private $_webGrabber; public function __construct() { $this->_dataStorage = new FileDataStorage(); $this->_webGrabber = new CurlWebGrabber(); } // ... Здесь какие-то методы для построения карты сайта }
  26. 26. Освобождаем SiteMapBuilder от зависимостей (инвертируем зависимости) class siteMapBilder { private $_dataStorage; private $_webGrabber; public function __construct(WebGrabber $dataStorage, DataStorage $webGrabber) { $this->_dataStorage = $dataStorage; $this->_webGrabber = $webGrabber; } // ... Здесь какие-то методы для построения карты сайта }
  27. 27. «... любые хорошо структурированные объектно-ориентированные архитектуры имеют четко определенные слои, каждый из которых поддерживает некоторый компактный набор служб с помошью хорошо определенного и контролируемого интерфейса» Г. Буч Разделение архитектуры по слоям
  28. 28. Усовершенствованное разделение архитектуры по слоям Здесь мы избавились не только от транзитивной зависимости между TopLayer и DeepLayer, но и от прямых зависимостей
  29. 29. Спасибо за внимание Вопросы?
  30. 30. Почитать за чашкой кофе * <ul><li>Приручите программные зависимости для большей гибкости приложений (Джеймс Ковач): http://msdn.microsoft.com/ru-ru/magazine/cc337885.aspx
  31. 31. Принцип подстановки Барбары Лисков (Василий Меленчук): http://habrahabr.ru/blogs/programming/83269/
  32. 32. Принципы проектирования классов (S.O.L.I.D.) (Александр Бындю): http://blog.byndyu.ru/2009/10/solid.html
  33. 33. SRP (Robert C. Martin): www.objectmentor.com/resources/articles/srp.pdf
  34. 34. OCP (Robert C. Martin): http://www.objectmentor.com/resources/articles/ocp.pdf
  35. 35. LSP (Robert C. Martin): http://www.objectmentor.com/resources/articles/lsp.pdf
  36. 36. ISP (Robert C. Martin): http://www.objectmentor.com/resources/articles/isp.pdf
  37. 37. DIP (Robert C. Martin): http://www.objectmentor.com/resources/articles/dip.pdf
  38. 38. * фоновая картинка здесь используется исключительно для красоты :-) </li></ul>

×