В предыдущем посте мы рассмотрели принципы, которых следует избегать при написании кода. Вы можете прочитать это здесь". В этом посте мы рассмотрим принципы проектирования SOLID, стандарты программирования, которые все разработчики должны хорошо понимать, чтобы создавать хорошую архитектуру.
SOLID — это мнемоническая аббревиатура:
- Единая ответственность
- Открыто закрыто
- Замена Лискова
- Разделение интерфейса
- Инверсия зависимости
Единая ответственность
- Каждому объекту должна быть назначена одна единственная ответственность.
- Конкретный класс должен решать конкретную задачу.
ГЛУПЫЙ пример
class Order
{
public function calculateTotalSum() {...}
public function getItems() {...}
public function getItemCount() {...}
public function addItem() {...}
public function deleteItem() {...}
public function load() {...}
public function save() {...}
public function update() {...}
public function delete() {...}
public function printOrder() {...}
public function showOrder() {...}
}
НАДЕЖНЫЙ пример
class Order
{
public function calculateTotalSum() {...}
public function getItems() {...}
public function getItemCount() {...}
public function addItem() {...}
public function deleteItem() {...}
}
class OrderRepository
{
public function load() {...}
public function save() {...}
public function update() {...}
public function delete() {...}
}
class OrderViewer
{
public function printOrder() {...}
public function showOrder() {...}
}
Открыто закрыто
- Программные объекты должны быть открыты для расширения, но закрыты для модификации.
- Все классы, функции и т.п. должны быть спроектированы так, чтобы для изменения их поведения не требовалось изменять их исходный код.
ГЛУПЫЙ пример
class OrderRepository
{
public function load($orderId)
{
$pdo = new PDO($this->config->getDsn(), $this->config->getDBUser(), $this->config->getDBPassword());
$statement = $pdo->prepare('SELECT * FROM `orders` WHERE id=:id');
$statement->execute(array(':id' => $orderId));
return $query->fetchObject('Order');
}
public function save($order) {...}
public function update($order) {...}
public function delete($order) {...}
}
НАДЕЖНЫЙ пример
{
private $source;
public function setSource(IOrderSource $source)
{
$this->source = $source;
}
public function load($orderId)
{
return $this->source->load($orderId);
}
public function save($order)
{
return $this->source->save($order);
}
public function update($order) {...};
public function delete($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) {...}
}
Замена Лискова
- Объекты в программе могут быть заменены их наследниками без изменения свойств программы.
- При использовании наследника класса результат выполнения кода должен быть предсказуемым и не изменять свойства метода.
- Должна быть возможность замены любого подтипа базового типа.
- Функции, использующие ссылки на базовые классы, должны иметь возможность использовать объекты производных классов, не зная об этом.
ГЛУПЫЙ пример
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 ???
НАДЕЖНЫЙ пример
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
{
protected $size;
public setSize($size)
{
$this->size = $size;
}
public function getSize()
{
return $this->size;
}
}
Разделение интерфейса
- Много специализированных интерфейсов лучше, чем один универсальный.
- Соблюдение этого принципа необходимо для того, чтобы клиентские классы, использующие/реализующие интерфейс, знали только о тех методах, которые они используют, что приводит к уменьшению количества неиспользуемого кода.
ГЛУПЫЙ пример
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);
}
НАДЕЖНЫЙ пример
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 Customer
{
private $currentOrder = null;
public function buyItems()
{
if (is_null($this->currentOrder)) {
return false;
}
$processor = new OrderProcessor(); // !!!
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);
}
}
class OrderProcessor
{
public function checkout($order) {...}
}
НАДЕЖНЫЙ пример
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) {...}
}
Резюме
Принцип единой ответственности
Каждому объекту должна быть назначена одна ответственность». Для этого проверяем, сколько у нас причин для смены класса — если больше одной, то этот класс нужно разбить.
Принцип открытости/закрытости (Open-closed)
Программные объекты должны быть открыты для расширения, но закрыты для модификации». Для этого мы представим наш класс как «черный ящик» и посмотрим, сможем ли мы изменить его поведение в этом случае.
Принцип подстановки Варвары Лисков (подстановка Лисков)
Объекты в программе могут быть заменены их наследниками без изменения свойств программы». Для этого проверяем, усилили ли мы предусловия и ослабили ли постусловия. Если это происходит, то принцип не соблюдается.
Разделение интерфейса
Многие специализированные интерфейсы лучше одного универсального. Мы проверяем, насколько интерфейс содержит методы и насколько разные функции накладываются на эти методы, и при необходимости разбиваем интерфейсы.
Принцип инверсии зависимости
Зависимости должны строиться на абстракциях, а не на деталях». Мы проверяем, зависят ли классы от каких-то других классов (непосредственно инстанцируют объекты других классов и т. д.), и если такая зависимость имеет место, заменяем ее зависимостью от абстракции.
Первоначально опубликовано на https://it.badykov.com 14 марта 2020 г.