Принципы SOLID
- S: Single Responsibility Principle (Принцип единственной ответственности).
- O: Open-Closed Principle (Принцип открытости-закрытости).
- L: Liskov Substitution Principle (Принцип подстановки Барбары Лисков).
- I: Interface Segregation Principle (Принцип разделения интерфейса).
- D: Dependency Inversion Principle (Принцип инверсии зависимостей).
1. S - Принцип единственной ответственности
Класс должен быть ответственен лишь за что-то одно. Если класс отвечает за решение нескольких задач, его подсистемы, реализующие решение этих задач, оказываются связанными друг с другом. Изменения в одной такой подсистеме ведут к изменениям в другой.
class Char
{
private $name;
public function getName()
{
return $this->name;
}
public function saveChar()
{
// save $this
}
}
Класс Char описывает какого-то персонажа. И этот класс нарушает принцип единственной ответственности. Ведь, в соответствии с этим принципом, класс должен решать лишь какую-то одну задачу. Он же решает две, занимаясь работой с хранилищем данных в методе saveChar и манипулируя свойствами объекта в методе getName.
Если изменится порядок работы с хранилищем данных, используемым приложением, то придётся вносить изменения во все классы, работающие с хранилищем. Такая архитектура не отличается гибкостью, изменения одних подсистем затрагивают другие, что напоминает эффект домино.
Для того чтобы привести вышеприведённый код в соответствие с принципом единственной ответственности, создадим ещё один класс, единственной задачей которого является работа с хранилищем, в частности — сохранение в нём объектов класса SportCar
class Char
{
private $name;
public function getName()
{
return $this->name;
}
}
class SaveChar
{
public function saveChar(Char $char)
{
// save $char
}
}
2. O - Принцип открытости-закрытости
Программные сущности (классы, модули, функции) должны быть открыты для расширения, но не для модификации.
Мы хотим перебрать список персонажей, и узнать о том, чем они занимаются. Представим, что мы решаем эту задачу с помощью функции charBehavior
class Orc
{
//...
}
class Dwarf
{
//...
}
class WhatDo
{
//...
public function charBehavior($char)
{
$behavior = null;
if($char instanceof Orc) {
$behavior = 'Точит топор';
} else if($char instanceof Dwarf) {
$behavior = 'Чинит доспехи';
}
echo $behavior;
return;
}
}
Самая главная проблема такой архитектуры заключается в том, что функция определяет то, чем занят тот или иной персонаж, анализируя конкретные объекты. Функция charBehavior не соответствует принципу открытости-закрытости, так как, например, при появлении новых персонажей, нам, для того, чтобы с её помощью можно было бы узнавать их поведение, придётся её изменить.
Например появился эльф:
class Elf
{
//...
}
После этого нам придётся поменять код функции charBehavior
//...
public function charBehavior($char)
{
$behavior = null;
if($char instanceof Orc) {
$behavior = 'Точит топор';
} else if($char instanceof Dwarf) {
$behavior = 'Чинит доспехи';
} else if ($char instanceof Elf) {
$behavior = 'Спрятался в лесу';
}
echo $behavior;
return;
}
Как видите, при добавлении в массив нового персонажа придётся дополнять код функции, добавляя в неё новые выражения if.
Решение здесь состоит в том, чтобы создать простой интерфейс Char, который имеет метод charBehavior и будет реализован всеми другими персонажами.
interface Char
{
//...
public function charBehavior()
}
class Orc implements Char
{
//...
public function charBehavior()
{
return 'Точит топор';
}
}
class Dwarf implements Char
{
//...
public function charBehavior()
{
return 'Чинит доспехи';
}
}
class Elf implements Char
{
//...
public function charBehavior()
{
return 'Спрятался в лесу';
}
}
class WhatDo
{
//...
public function behavior($char)
{
echo $char->charBehavior();
return;
}
}
3. L - Принцип подстановки Барбары Лисков
Необходимо, чтобы подклассы могли бы служить заменой для своих родителей.
Цель этого принципа заключаются в том, чтобы классы-наследники могли бы использоваться вместо родительских классов, от которых они образованы, не нарушая работу программы. Если оказывается, что в коде проверяется тип класса, значит принцип подстановки нарушается.
Оставим в покое орков и попьем кофе... Честно говоря, мне просто очень понравился этот пример.
Итак, представьте, что у Вас две кофемашины - базовая и премиальная. Кофемашины одинаковы, единственная разница в том, что премиальная машина делает и ESPRESSO и AMERICANO, а базовая - только ESPRESSO. Основные действия программы должны быть одинаковы для обеих машин.
interface CoffeeMachineInterface {
public function brewCoffee($selection);
}
class BasicCoffeeMachine implements CoffeeMachineInterface {
public function brewCoffee($selection) {
switch ($selection) {
case 'ESPRESSO':
return $this->brewEspresso();
default:
throw new CoffeeException('Selection not supported');
}
}
protected function brewEspresso() {
// Brew an espresso...
}
}
class PremiumCoffeeMachine extends BasicCoffeeMachine {
public function brewCoffee($selection) {
switch ($selection) {
case 'ESPRESSO':
return $this->brewEspresso();
case 'AMERICANO':
return $this->brewAmericanoCoffee();
default:
throw new CoffeeException('Selection not supported');
}
}
protected function brewAmericanoCoffee() {
// Brew a americano coffee...
}
}
function getCoffeeMachine(User $user) {
switch ($user->getPlan()) {
case 'PREMIUM':
return new PremiumCoffeeMachine();
case 'BASIC':
default:
return new BasicCoffeeMachine();
}
}
function prepareCoffee(User $user, $selection) {
$coffeeMachine = getCoffeeMachine($user);
return $coffeeMachine->brewCoffee($selection);
}
4. I - Принцип разделения интерфейса
Вкратце - создавайте узкоспециализированные интерфейсы, предназначенные для конкретного клиента. Клиенты не должны зависеть от интерфейсов, которые они не используют.
Возвращаясь к фентезийной теме, создадим интерфейс владения оружием
interface WeaponInterface {
public function shotAxe();
public function shotBow();
}
/**
* Человек - универсален
* И из лука стреляет и топопром махать может :-)
*/
class Human implements WeaponInterface {
public function shotAxe() {
echo 'Удар топором!';
}
public function shotBow() {
echo 'Выстрел из лука!';
}
}
class Orc implements WeaponInterface {
public function shotAxe() {
echo 'Удар топором!';
}
public function shotBow() {
throw new Exception('Я не умею стрелять из лука!');
}
}
class Elf implements WeaponInterface {
public function shotAxe() {
throw new Exception('Я не могу поднять топор!');
}
public function shotBow() {
echo 'Выстрел из лука!';
}
}
Проблема, как видите, в том, что у орка и эльфа есть методы, которые не используются. Решение состоит в том, чтобы разделить WeaponInterface на два более конкретных интерфейса, которые используются только тогда, когда это необходимо.
interface OrcInterface {
public function shotAxe();
}
interface ElfInterface {
public function shotBow();
}
class Human implements OrcInterface, ElfInterface {
public function shotAxe() {
echo 'Удар топором!';
}
public function shotBow() {
echo 'Выстрел из лука!';
}
}
class Orc implements OrcInterface {
public function shotAxe() {
echo 'Удар топором!';
}
}
class Elf implements ElfInterface {
public function shotBow() {
echo 'Выстрел из лука!';
}
}
5. D - Принцип инверсии зависимостей
Вкратце - этот принцип означает, что конкретный класс не должен напрямую зависеть от другого класса, а должен зависеть от абстракции этого класса. Или другими словами - объектом зависимости должна быть абстракция, а не что-то конкретное.
Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
Ну и самый ходовой на просторах Интернета пример
class UserDB {
private $dbConnection;
public function __construct(MySQLConnection $dbConnection) {
$this->$dbConnection = $dbConnection;
}
public function store(User $user) {
// Store the user into a database...
}
}
В этом случае класс UserDB напрямую зависит от базы данных MySQL. Это означает, что если мы хотим изменить используемый механизм базы данных, нам нужно переписать этот класс и нарушить принцип открытия-закрытия.
Решение состоит в том, чтобы разработать абстракцию подключения к базе данных
interface DBConnectionInterface {
public function connect();
}
class MySQLConnection implements DBConnectionInterface {
public function connect() {
// Return the MySQL connection...
}
}
class UserDB {
private $dbConnection;
public function __construct(DBConnectionInterface $dbConnection) {
$this->dbConnection = $dbConnection;
}
public function store(User $user) {
// Store the user into a database...
}
}
Важлива інформація
України
втрати противника
орієнтовно склали: