Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

21,043 views

Published on

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

Published in: Technology
  • Be the first to comment

Принципы проектирования 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>

×