![]() |
+7 (495) 229-0436 | ![]() |
shopadmin@itshop.ru | 119334, г. Москва, ул. Бардина, д. 4, корп. 3 | ![]() |
![]() |
![]() |
|
|
Шпаргалка по SOLID-принципам с примерами на PHP16.01.2014 16:42
Тема SOLID-принципов и в целом чистоты кода не раз поднималась на Хабре и, возможно, уже порядком изъезженная. Но тем не менее, не так давно мне приходилось проходить собеседования в одну интересную IT-компанию, где меня попросили рассказать о принципах SOLID с примерами и ситуациями, когда я не соблюл эти принципы и к чему это привело. И в тот момент я понял, что на каком-то подсознательном уровне я понимаю эти принципы и даже могут назвать их все, но привести лаконичные и понятные примеры для меня стало проблемой. Поэтому я и решил для себя самого и для сообщества обобщить информацию по SOLID-принципам для ещё лучшего её понимания. Статья должна быть полезной, для людей только знакомящихся с SOLID-принципами, также, как и для людей "съевших собаку" на SOLID-принципах.
Что же такое SOLID-принципы? Если верить определению Wikipedia, это: аббревиатура пяти основных принципов дизайна классов в объектно-ориентированном проектировании - Single responsibility, Open-closed, Liskov substitution, Interface segregation и Dependency inversion. Таким образом, мы имеем 5 принципов, которые и рассмотрим ниже:
Принцип единственной ответственности (Single responsibility)Итак, в качества примера возьмём довольно популярный и широкоиспользуемый пример - интернет-магазин с заказами, товарами и покупателями. Принцип единственной ответственности гласит - "На каждый объект должна быть возложена одна единственная обязанность" . Т.е. другими словами - конкретный класс должен решать конкретную задачу - ни больше, ни меньше. Рассмотрим следующее описание класса для представления заказа в интернет-магазине: Как можно увидеть, данный класс выполняет операций для 3 различный типов задач: работа с самим заказом(
Теперь каждый класс занимается своей конкретной задачей и для каждого класса есть только 1 причина для его изменения.
Принцип открытости/закрытости (Open-closed)Данный принцип гласит - В данном случае хранилищем у нас является база данных. например, MySQL. Но вдруг мы захотели подгружать наши данные о заказах, например, через API стороннего сервера, который, допустим, берёт данные из 1С. Какие изменения нам надо будет внести? Есть несколько вариантов, например, непосредственно изменить методы класса
Интерфейс IOrderSource и его реализация и использование
class OrderRepository { private $source; public function setSource(IOrderSource $source) { $this->source = $source; } public function load($orderID) { return $this->source->load($orderID); } public function save($order){/*...*/} public function update($order){/*...*/} } interface IOrderSource { public function load($orderID); public function save($order); public function update($order); public function delete($order); } class MySQLOrderSource implements IOrderSource { public function load($orderID); public function save($order){/*...*/} public function update($order){/*...*/} public function delete($order){/*...*/} } class ApiOrderSource implements IOrderSource { public function load($orderID); public function save($order){/*...*/} public function update($order){/*...*/} public function delete($order){/*...*/} } Таким образом, мы можем изменить источник и соответственно поведение для класса
Принцип подстановки Барбары Лисков (Liskov substitution)Пожалуй, принцип, который вызывает самые большие затруднения в понимании.
Пример иерархии прямоугольника и квадрата и вычислении их площади
class Rectangle { protected $width; protected $height; public setWidth($width) { $this->width = $width; } public setHeight($height) { $this->height = $height; } public function getWidth() { return $this->width; } public function getHeight() { return $this->height; } } class Square extends Rectangle { public setWidth($width) { parent::setWidth($width); parent::setHeight($width); } public setHeight($height) { parent::setHeight($height); parent::setWidth($height); } } function calculateRectangleSquare(Rectangle $rectangle, $width, $height) { $rectangle->setWidth($width); $rectangle->setHeight($height); return $rectangle->getHeight * $rectangle->getWidth; } calculateRectangleSquare(new Rectangle, 4, 5); // 20 calculateRectangleSquare(new Square, 4, 5); // 25 ??? Очевидно, что такой код явно выполняется не так, как от него этого ждут. Тогда же как решить проблему?
"Что ещё за пред- и постусловия?" - можете спросите Вы. Вернёмся к нашему примеру и посмотрим, как мы изменили пред- и постусловия. Поэтому, лучше в рамках ООП и задачи расчёта площади фигуры не делать иерархию "квадрат" наследует "прямоугольник", а сделать их как 2 отдельные сущности: Хороший реальный пример несоблюдения принципа Лискоу и решения, принятого в связи с этим, рассмотрен в книге Роберта Мартина "Быстрая разработка программ" в разделе "Принцип подстановки Лискоу. Реальный пример".
Принцип разделения интерфейса (Interface segregation)Данный принцип гласит, что "Много специализированных интерфейсов лучше, чем один универсальный" Вернёмся примеру с интернет-магазином. Данный интефейс плох тем, что он включает слишком много методов. А что, если наш класс товаров не может иметь скидок или промокодов, либо для него нет смысла устанавливать материал из которого сделан (например, для книг). Таким образом, чтобы не реализовывать в каждом классе неиспользуемые в нём методы, лучше разбить интерфейс на несколько мелких и каждым конкретным классом реализовывать нужные интерфейсы.
Разбиение интерфейса IItem на несколько
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); } 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){/*...*/} }
Принцип инверсии зависимостей (Dependency Invertion)Принцип гласит - "Зависимости внутри системы строятся на основе абстракций. Модули верхнего уровня не зависят от модулей нижнего уровня. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций" . Данное определение можно сократить - "зависимости должны строится относительно абстракций, а не деталей" . Для примера рассмотрим оплату заказа покупателем.
Всё кажется вполне логичным и закономерным. Но есть одна проблема - класс
Инвертирование зависимости класса Customer
class Customer { private $currentOrder = null; public function buyItems(IOrderProcessor $processor) { if(is_null($this->currentOrder)){ return false; } return $processor->checkout($this->currentOrder); } public function addItem($item){ if(is_null($this->currentOrder)){ $this->currentOrder = new Order(); } return $this->currentOrder->addItem($item); } public function deleteItem($item){ if(is_null($this->currentOrder)){ return false; } return $this->currentOrder ->deleteItem($item); } } interface IOrderProcessor { public function checkout($order); } class OrderProcessor implements IOrderProcessor { public function checkout($order){/*...*/} } Таким образом, класс
ШпаргалкаРезюмируя всё выше изложенное, хотелось бы сделать следующую шпаргалку
Надеюсь, моя "шпаргалка" поможет кому-нибудь в понимании принципов SOLID и даст толчок к их использованию в своих проектах. P.S. В комментариях посоветовали хорошую книгу - Роберт Мартин "Быстрая разработка программ". Там очень подробно и с примерами описаны принципы SOLID. |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
О нас |
Интернет-магазин ITShop.ru предлагает широкий спектр услуг информационных технологий и ПО.
На протяжении многих лет интернет-магазин предлагает товары и услуги, ориентированные на бизнес-пользователей и специалистов по информационным технологиям. Хорошие отзывы постоянных клиентов и высокий уровень специалистов позволяет получить наивысший результат при совместной работе. В нашем магазине вы можете приобрести лицензионное ПО выбрав необходимое из широкого спектра и ассортимента по самым доступным ценам. Наши менеджеры любезно помогут определиться с выбором ПО, которое необходимо именно вам. Также мы проводим учебные курсы. Мы приглашаем к сотрудничеству учебные центры, организаторов семинаров и бизнес-тренингов, преподавателей. Сфера сотрудничества - продвижение бизнес-тренингов и курсов обучения по информационным технологиям.
|
119334, г. Москва, ул. Бардина, д. 4, корп. 3 +7 (495) 229-0436 shopadmin@itshop.ru |
|
© ООО "Interface Ltd." Продаем программное обеспечение с 1990 года |