SOLID Principles
1.S - Принцип на единствената отговорност (Single Responsibility Principle, SRP)
Един клас трябва да има само една причина да се променя.
Пример:
class User {
public function createUser() { /* ... */ }
public function deleteUser() { /* ... */ }
public function logError($error) { /* записване на грешка в лог */ }
}
Това нарушава SRP, тъй като имаме две различни отговорности. По-добър подход би бил:
class User {
public function createUser() { /* ... */ }
public function deleteUser() { /* ... */ }
}
class Logger {
public function logError($error) { /* записване на грешка в лог */ }
}
2. O - Принцип на отвореност-затвореност (Open-Closed Principle, OCP)
Класовете трябва да са отворени за разширение, но затворени за промяна.
Пример:
Ако имаме клас, който изчислява общата сума на поръчка:
class OrderTotal {
public function calculateTotal($order) {
return $order->price + ($order->price * 0.2); // добавя 20% ДДС
}
}
Ако искаме да добавим различни такси, трябва да променяме класа. Вместо това можем да разширим функционалността чрез полиморфизъм:
interface Tax {
public function apply($price);
}
class VATTax implements Tax {
public function apply($price) {
return $price * 0.2;
}
}
class OrderTotal {
protected $tax;
public function __construct(Tax $tax) {
$this->tax = $tax;
}
public function calculateTotal($order) {
return $order->price + $this->tax->apply($order->price);
}
}
L - Принцип на заменяемост на Лисков (Liskov Substitution Principle, LSP)
Обекти от базов клас трябва да могат да бъдат заменени с обекти от подклас без да променят коректното поведение на програмата.
Пример:
class Rectangle {
protected $width;
protected $height;
public function setWidth($width) { $this->width = $width; }
public function setHeight($height) { $this->height = $height; }
public function getArea() { return $this->width * $this->height; }
}
class Square extends Rectangle {
public function setWidth($width) {
$this->width = $width;
$this->height = $width; // това нарушава LSP
}
public function setHeight($height) {
$this->height = $height;
$this->width = $height; // това нарушава LSP
}
}
Вместо да наследяваме "Rectangle", трябва да имаме разделени абстракции за двата типа фигури или да използваме композиция.
I - Принцип на разделение на интерфейсите (Interface Segregation Principle, ISP)
Никой не трябва да бъде принуден да зависи от интерфейси, които не използва.
Пример:
interface Worker {
public function work();
public function eat();
}
class HumanWorker implements Worker {
public function work() { /* ... */ }
public function eat() { /* ... */ }
}
class RobotWorker implements Worker {
public function work() { /* ... */ }
public function eat() { /* Robots don't eat! This method doesn't make sense. */ }
}
Вместо това:
interface Workable {
public function work();
}
interface Eatable {
public function eat();
}
class HumanWorker implements Workable, Eatable {
public function work() { /* ... */ }
public function eat() { /* ... */ }
}
class RobotWorker implements Workable {
public function work() { /* ... */ }
}
D - Принцип на обратната зависимост (Dependency Inversion Principle, DIP)
Нивата на високо ниво не трябва да зависят от нивата на ниско ниво. И двете трябва да зависят от абстракции.
Пример:
class LightBulb {
public function turnOn() { /* ... */ }
public function turnOff() { /* ... */ }
}
class Switch {
private $bulb;
public function __construct(LightBulb $bulb) {
$this->bulb = $bulb;
}
public function operate() {
// ...
}
}
Това нарушава DIP, защото "Switch" зависи директно от "LightBulb". По-добро решение би било да използваме интерфейс:
interface Switchable {
public function turnOn();
public function turnOff();
}
class LightBulb implements Switchable {
public function turnOn() { /* ... */ }
public function turnOff() { /* ... */ }
}
class Switch {
private $device;
public function __construct(Switchable $device)