├── .gitignore ├── img ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png ├── 8.png ├── 9.png ├── 10.png ├── 11.png ├── 12.png ├── 13.png ├── 14.png ├── 15.png ├── 16.png ├── 17.png └── 18.png └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /* 2 | !/img 3 | !.gitignore 4 | !README.md 5 | -------------------------------------------------------------------------------- /img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atlance/solid-principles/HEAD/img/1.png -------------------------------------------------------------------------------- /img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atlance/solid-principles/HEAD/img/2.png -------------------------------------------------------------------------------- /img/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atlance/solid-principles/HEAD/img/3.png -------------------------------------------------------------------------------- /img/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atlance/solid-principles/HEAD/img/4.png -------------------------------------------------------------------------------- /img/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atlance/solid-principles/HEAD/img/5.png -------------------------------------------------------------------------------- /img/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atlance/solid-principles/HEAD/img/6.png -------------------------------------------------------------------------------- /img/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atlance/solid-principles/HEAD/img/7.png -------------------------------------------------------------------------------- /img/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atlance/solid-principles/HEAD/img/8.png -------------------------------------------------------------------------------- /img/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atlance/solid-principles/HEAD/img/9.png -------------------------------------------------------------------------------- /img/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atlance/solid-principles/HEAD/img/10.png -------------------------------------------------------------------------------- /img/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atlance/solid-principles/HEAD/img/11.png -------------------------------------------------------------------------------- /img/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atlance/solid-principles/HEAD/img/12.png -------------------------------------------------------------------------------- /img/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atlance/solid-principles/HEAD/img/13.png -------------------------------------------------------------------------------- /img/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atlance/solid-principles/HEAD/img/14.png -------------------------------------------------------------------------------- /img/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atlance/solid-principles/HEAD/img/15.png -------------------------------------------------------------------------------- /img/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atlance/solid-principles/HEAD/img/16.png -------------------------------------------------------------------------------- /img/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atlance/solid-principles/HEAD/img/17.png -------------------------------------------------------------------------------- /img/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atlance/solid-principles/HEAD/img/18.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SOLID «S»: Принцип единственной обязанности (single responsibility principle, SPR) 2 | === 3 | 4 | Принцип единственной обязанности - один из принципов, которого следует придерживаться при 5 | написании кода. Он декларирует, что каждый объект должен иметь одну 6 | единственную обязанность и эта обязанность должна быть инкапсулирована в 7 | класс. 8 | 9 | Термин **«SOLID»** представляет собой аббревиатуру пяти важнейших принципов 10 | работы с классами в объектно-ориентированном проектировании: 11 | 12 | 1. [Принципа 13 | единственной обязанности](#S) 14 | 2. [Принципа 15 | открытости/закрытости](#O) 16 | 3. [Принципа подстановки Барбары 17 | Лисков](#L) 18 | 4. [Принципа разделения 19 | интерфейса](#I) 20 | 5. [Принципа инверсии 21 | зависимостей](#D) 22 | 23 | Этими пятью гибкими принципами следует руководствоваться при написании 24 | кода. 25 | 26 | ### Определение 27 | 28 | **Класс должен иметь одну и только одну причину для изменений.** 29 | 30 | Это один из 5 гибких принципов SOLID, определенных в книге «Быстрая 31 | разработка программ. Принципы, примеры, практика» Робертом С. Мартином. 32 | Затем эта книга была переиздана в версии для C\# «Принципы, паттерны и 33 | методики гибкой разработки на языке C\#». То, что декларирует данный 34 | принцип, вполне легко понять, однако не так легко  реализовать на 35 | практике. 36 | 37 | Класс должен иметь одну единственную причину для изменений. Но чем 38 | обусловлена подобная необходимость? В компилируемых статически 39 | типизированных языках программирования существование нескольких причин 40 | может повлечь за собой ряд нежелательных изменений. 41 | 42 | Две различные причины для изменений подобны двум различным командам, 43 | которые могут работать с одним кодом, и каждая из них будет 44 | разворачивать свое собственное решение, которое в случае C++, C\# Java 45 | или других компилируемых языков, может привести к несовместимости между 46 | различными частями приложения. 47 | 48 | Даже если вы не используете компилируемый язык, вам, возможно, 49 | потребуется повторное тестирование определенного класса или модуля по 50 | различным причинам. Это означает, что вы потратите больше времени и 51 | усилий. 52 | 53 | ### Аудитория 54 | 55 | Определение одной единственной ответственности, которую должен иметь 56 | класс или модуль, является гораздо более сложной задачей, чем простое 57 | просматривание списка. 58 | 59 | Например, один из способов поиска причин изменения - анализ аудитории 60 | класса. Пользователи приложения, которые обслуживаются определенным 61 | модулем, будут являться теми лицами, которые требуют изменений класса. 62 | Вот несколько модулей и их возможная аудитория. 63 | 64 | 1. Модуль сохраняемости - аудитория включает администраторов баз данных 65 | и проектировщиков ПО. 66 | 2. Модуль отчетности - аудитория включает клерков, бухгалтеров и 67 | операционистов. 68 | 3. Модуль расчета платежей для системы расчета заработной платы - 69 | аудитория может включать юристов, менеджеров и бухгалтеров. 70 | 4. Модуль поиска книг в библиотеке - аудитория может включать 71 | библиотекаря и читателей. 72 | 73 | ### Роли и актеры 74 | 75 | Ассоциирование конкретных лиц с ролями – весьма непростая задача. В 76 | небольшой фирме одно единственное лицо может играть сразу несколько 77 | ролей, в то время как в крупной организации может быть привлечено 78 | несколько исполнителей для выполнения одной функции. Поэтому, пожалуй, 79 | будет немаловажно подумать об этих ролях. Однако определить сами роли 80 | бывает достаточно сложно. Гораздо легче обозначить актёров, играющих эти 81 | роли и связать актёров и нашу аудиторию. 82 | 83 | Таким образом, если аудитория обусловливает причины изменения, актёры 84 | определяют аудиторию. Это позволит нам свести понятия конкретных лиц 85 | вроде «Архитектора Джона» или «Секретаря Марии» к операциям. 86 | 87 | Таким образом, согласно Роберту С. Мартину, ответственность - это 88 | определенный набор функций, которые выполняет один взятый актёр. 89 | 90 | ### Источник изменений 91 | 92 | Рассуждая таким образом, можно сделать вывод о том, что актёры 93 | представляют собой источник изменений для набора функций, которые 94 | удовлетворяют потребности самих актёров. Наряду с изменением 95 | потребностей актёров, этот специфичный набор функций тоже должен 96 | измениться и приспособиться под новые потребности актеров. 97 | 98 | **Актер для ответственности - единственный источник изменений этой 99 | ответственности. (Роберт С. Мартин)** 100 | 101 | ### Классические примеры 102 | 103 | Объекты, которые могут распечатывать сами себя 104 | 105 | Предположим, что у нас есть класс `Book`, который инкапсулирует в себе 106 | книгу вместе с её функциональностью. 107 | 108 | ```php 109 | getTitle(). ' - ' . $this->getAuthor(); 236 | file_put_contents($filename, serialize($this)); 237 | } 238 | } 239 | ``` 240 | Мы снова можем определить несколько актёров, например, Систему 241 | управления книгой и Сохраняемость. Этот класс подлежит модификации 242 | каждый раз при измении сохраняемости или способа перелистывания 243 | страницы. Можно отметить несколько вариантов изменения данных. 244 | ```php 245 | getTitle() . ' - ' . $book->getAuthor(); 276 | file_put_contents($filename, serialize($book)); 277 | } 278 | } 279 | ``` 280 | 281 | Переместив метод сохранения объекта в другой класс, мы сможем явно 282 | разделить ответственность и легко изменить данный метод сохраняемости, 283 | никак не влияя на класс Book. Так, внедрение класса `DatabasePersistence` 284 | будет абсолютно тривиальным, и наша бизнес-логика, выстроенная вокруг 285 | действий с книгой никак не изменится. 286 | 287 | ### Высокоуровневое представление 288 | 289 | В своих предыдущих статьях я не раз упоминал и предлагал Вашему вниманию 290 | схему высокоуровневой архитектуры, которая приведена ниже. 291 | 292 |

293 | 294 |

295 | 296 | Если мы проанализируем данную схему, то сразу поймем, как соблюдается 297 | ПЕО. Создание нового объекта обозначено с правой стороны схемы с помощью 298 | **«Фабрик» (Factories)** и единой точки входа нашего приложения (Main). Один 299 | актёр - одна ответственность. О сохраняемости **(Persistence)** также 300 | позаботились, расположив ее внизу. Отдельный модуль предназначается для 301 | отдельной ответственности. И наконец, с левой стороны мы разместили 302 | представление, или механизм доставки, в виде MVC или каком-либо другом 303 | типе пользовательского интерфейса. И вновь соблюден [принцип единой 304 | ответственности](#S). Все, что нам остается выяснить, - это что делать с 305 | самой бизнес-логикой. 306 | 307 | ### Вопросы проектирования программного обеспечения 308 | 309 | Когда мы обдумываем, как лучше написать программное обеспечение, нам 310 | обычно приходится принимать во внимание множество различных аспектов. 311 | Например, несколько требований, предъявляемых к классу и оказывающих на 312 | него влияние, могут быть представлены в виде оси изменений. Эти оси 313 | изменений могут играть роль ключа к единой ответственности. Вполне 314 | вероятно, что группы требований, влияющих на ту же группу функций, будут 315 | иметь причины для изменений или могут быть выдвинуты на первый план. 316 | 317 | Главное достоинство ПО - простота внесения изменений. Следующее важное 318 | качество - функциональность с точки зрения способности ПО удовлетворять 319 | как можно более широкий круг потребностей пользователей. Однако для 320 | достижения высокого значения на втором уровне, сначала нужно обязательно 321 | выяснить значение первого критерия. Чтобы поднять значение первичного 322 | критерия на должный уровень, мы должны, соблюдая ПЕО, построить такую 323 | архитектуру, которую можно легко модифицировать, расширять и в которую 324 | можно быстро добавлять новую функциональность. 325 | 326 | ### Изложим по шагам: 327 | 328 | Значение первого критерия должно быть установлено до определения 329 | значения второго критерия. 330 | 331 | 1. Второй критерий отвечает за удовлетворение потребностей 332 | пользователей. 333 | 2. Потребности пользователей – это потребности актёров. 334 | 3. Потребности актёров определяют необходимость изменения этих актёров. 335 | 4. Потребности в изменениях актёров, в свою очередь, определяют нашу 336 | ответственность. 337 | 338 | Таким образом, в процессе разработки архитектуры нашего программного 339 | обеспечения, мы должны: 340 | 341 | 1. Определить актёров. 342 | 2. Выявить область ответственности каждого актёра. 343 | 3. Сгруппировать классы и функции так, чтобы каждый из них отвечал 344 | только за свою часть. 345 | 346 | ### Менее очевидный пример 347 | ```php 348 | findBookBy($book->getTitle(), $book->getAuthor()); 437 | } 438 | 439 | } 440 | ``` 441 | Для того чтобы найти нужную книгу, библиотекарь должен будет применить 442 | класс `BookLocator`. Посетителю же потребуется только класс `Book`. Конечно 443 | же, `BookLocator` можно реализовать несколькими разными способами. Так, 444 | можно использовать автора книги и ее название, чтобы найти информацию из 445 | объекта `Book`. Это всегда зависит от нашей задачи. Важно то, что при 446 | переезде библиотеки в другое помещение организация хранения книг, скорее 447 | всего, изменится, и библиотекарю придется искать книги в новой 448 | библиотеке, но при этом объект `Book` затронут не будет. Точно также, если 449 | мы позволим читателям просматривать только аннотации книг, закрыв доступ 450 | к их страницам – мы никак не повлияем ни на библиотекаря, ни на 451 | собственно процесс поиска полок, на которых находятся книги. 452 | 453 | Однако если наша задача – исключить библиотекаря и разработать механизм 454 | самообслуживания в нашей библиотеке, то мы можем считать, что ПЕО был 455 | соблюден в нашем первом примере. Читатели в этом случае сами станут 456 | выступать в роли библиотекарей, и им придется идти искать книги 457 | самостоятельно, после чего подтверждать получение нужной книги в 458 | автоматизированной системе. Существует и такая возможность. Главное, что 459 | здесь нужно запомнить, - это то, что Вы всегда должны обдумывать свои 460 | задачи очень тщательно. 461 | 462 | ### Выводы 463 | 464 | Принцип [единcтвенной ответственности](#S) должен соблюдаться каждый раз, 465 | когда вы пишите код. Построение классов и модулей во многом определяется 466 | ПЕО, который позволяет сокращать зависимость между ними. Но, как и 467 | каждая медаль, ПЕО имеет две противоположные стороны. Очень удобно 468 | планировать архитектуру приложения, учитывая ПЕО, с самого начала 469 | разработки. Также удобно сразу выделить столько актеров, сколько нам 470 | понадобится. Однако, с точки зрения архитектуры, крайне опасно пытаться 471 | продумать все составляющие части приложения с самого начала. Излишнее 472 | соблюдение ПЕО может с легкостью привести к чрезмерной оптимизации, и 473 | вместо хорошей архитектуры, мы получим архитектуру, в которой будет 474 | очень сложно разобраться, какой класс или модуль за что отвечает. 475 | 476 | Таким образом, каждый раз, когда вы обнаруживаете, что класс или модуль 477 | может вскоре измениться по разным причинам, постарайтесь сделать 478 | необходимые шаги для соблюдения ПЕО, при этом не нужно сильно 479 | переусердствовать, так как излишняя оптимизация может, наоборот, нести 480 | за собой дополнительные проблемы. 481 | 482 | SOLID «O»: Принцип открытости/закрытости (open/closed principle, OCP) 483 | === 484 | 485 | Принцип открытости/закрытости легко 486 | нарушить, но и написать код, который соответствует этому принципу, не 487 | так уж и сложно. 488 | 489 | Сущность программного обеспечения (классы, модули и функции) должна быть 490 | открыта для усовершенствования, но закрыта для различных изменений. 491 | 492 | Принцип открытости-закрытости (OCP) был сформулирован французским 493 | программистом Бертраном Майером и впервые вышел в мир в его книге 494 | «Object-Oriented Software Construction» в 1988 году. Популярность к 495 | этому принципу пришла в начале 2000-х годов, когда его включили в SOLID. 496 | 497 | Здесь же речь идет о разработке модулей, классов и функций таким 498 | образом, чтобы в ситуации, когда понадобится новый функционал, не 499 | пришлось менять уже существующий код. Решение – в написании нового кода, 500 | который будет использовать существующий. Это может вызвать недоумение у 501 | разработчиков, которые пишут на Java, C, C++ или C\#, так как 502 | затрагивается не только исходный код, но и двоичный. Имеется в виду 503 | создание новых возможностей таким образом, чтобы не пришлось заново 504 | распределять двоичные файлы, файлы с расширением «exe» и DLL-библиотеки. 505 | 506 | ### SRP в контексте SOLID 507 | 508 | Если двигаться дальше, получится, что каждый новый принцип можно 509 | рассматривать в контексте уже рассмотренных ранее. Так, [принцип 510 | единственной обязанности](#S) (SRP) гласит, что на одном объекте может лежать 511 | только одна обязанность. Сравнивая **OCP** и **SRP**, можно отметить их 512 | комплементарность, взаимодополняемость. Код, разработанный с учетом **SRP**, 513 | визуально будет близок к такому же коду, но учитывающему **OCP**. Когда у 514 | нас есть код, каждый объект которого имеет одну обязанность, введение 515 | новой функции создаст вторую обязанность, второй повод для изменения. 516 | Это может нарушить оба принципа. 517 | 518 | Точно также, если у нас есть код, который должен меняться только тогда, 519 | когда его основные функции меняются или, наоборот, должны оставаться 520 | неизменными при добавлении новой функции, то в этом коде будут соблюдены 521 | оба принципа. Но это не значит, что **SRP**-принцип всегда приводит к **OCP**, 522 | или, наоборот, но в преобладающем большинстве случаев, если соблюден 523 | один принцип, привести код к соблюдению второго не составит большого 524 | труда. 525 | 526 | ### Очевидный пример нарушения принципа OCP 527 | 528 | С исключительно технической точки зрения принцип открытости-закрытости 529 | очень прост: между двумя классами есть простые связи, но один из классов 530 | нарушает принцип **OCP**. 531 | 532 |

533 | 534 |

535 | 536 | Класс `User` напрямую использует класс `Logic`. Если второй класс `Logic` 537 | нужно реализовать таким образом, чтобы можно было использовать и старые 538 | наработки, и новые, существующий класс `Logic` придется несколько 539 | изменить. `User` напрямую связан с классом `Logic`, а значит, способа 540 | изменить его так, чтобы не пришлось менять и `User`, попросту не 541 | существует. Когда речь идет о языках со статической типизацией, то класс 542 | `User`, скорее всего, тоже придется изменить. 543 | 544 | Когда же речь идет о компилируемых языках, то исполняемые файлы `User` и 545 | `Logic` и динамические библиотеки потребуют перекомпиляции клиентов, а это 546 | – крайне нежелательный процесс. 547 | 548 | ### Смотрим код 549 | 550 | Основываясь на размещенной выше схеме, можно сделать вывод о том, что 551 | если любой один класс использует другой класс, то [принцип 552 | открытости-закрытости](#O) будет нарушаться. Строго говоря, это – верно. 553 | Очень интересно найти тот самый предел, ту черту, за которой приходит 554 | понимание: соответствовать принципу **OCP** гораздо сложнее, чем изменить 555 | уже существующий код, или же затраты на изменение кода будут слишком 556 | большими. 557 | 558 | К примеру, нужно написать класс, который показывает прогресс закачки 559 | файла через некое приложение в процентах. Использоваться будет два 560 | основных класса - `Progress` и `File`. 561 | ```php 562 | public function testItCanGetTheProgressOfAFileAsAPercent() 563 | { 564 | $file = new File(); 565 | $file->length = 200; 566 | $file->sent = 100; 567 | 568 | $progress = new Progress($file); 569 | 570 | $this->assertEquals(50, $progress->getAsPercent()); 571 | } 572 | ``` 573 | В примере был использован `Progress`. В качестве результата нужно получить 574 | значение в процентах, независимо от фактического размера файла. Класс 575 | `File` был использован в качестве источника информации для класса 576 | `Progress`. У файла есть определенная длина в байтах и поле под названием 577 | `sent`, которое предоставляет объем данных, переданных загрузчику. В 578 | данный момент не важно, как именно эти значения будут обновляться в 579 | приложении. Можно предположить, что существует некая волшебная логика, 580 | которая это и делает, поэтому в примере их можно просто установить. 581 | ```php 582 | file = $file; 604 | } 605 | 606 | public function getAsPercent() 607 | { 608 | return $this->file->sent * 100 / $this->file->length; 609 | } 610 | } 611 | ``` 612 | Проще говоря, `Progress` – это класс, который принимает `File` в 613 | конструкторе. Для ясности тип переменной был определен в параметрах 614 | конструктора. Существует единственный полезный метод в `Progress`, это - 615 | `getAsPercent()`, который принимает отправляемые значения и длину из `File` 616 | и переводит все в проценты. Просто, понятно и работает. 617 | 618 | Testing started at 5:39 PM ... 619 | PHPUnit 3.7.28 by Sebastian Bergmann. 620 | . 621 | Time: 15 ms, Memory: 2.50Mb 622 | OK (1 test, 1 assertion) 623 | 624 | Код выглядит правильно, но он все равно нарушает [принцип 625 | открытости-закрытости](#O). Но, как и почему? 626 | 627 | ### Изменение требований 628 | 629 | Вполне ожидаемо, что каждое приложение будет эволюционировать по мере 630 | того, как появится нужда в новых функциях. Одной из новых возможностей 631 | некоего нашего приложения может стать не только закачка музыки, но и ее 632 | прослушивание. Длина `File` представлена в байтах, а продолжительность 633 | музыки – в секундах. Слушателям нужно предложить хороший показатель 634 | прогресса, но нельзя ли использовать для этого уже существующий? 635 | 636 | Оказывается, нет, нельзя, так как наш прогресс уже связан с `File` и 637 | понимает только файлы. Это не изменится даже тогда, когда мы сможем 638 | переделать его для распознавания музыки. Но для того, чтобы появилось 639 | распознавание музыкальных файлов, нужно, чтобы `Progress` имел 640 | представление о `Music` и о `File`. Если бы решение соответствовало принципу 641 | **OCP**, то `File` или `Progress` не пришлось бы менять, а существующий 642 | показатель прогресса можно было бы легко адаптировать к музыке. 643 | 644 | ### Решение 1: используем динамическую природу PHP 645 | 646 | У динамически типизированных языков есть преимущество: они могут 647 | распознавать тип объекта в процессе исполнения. Это позволяет не 648 | использовать отдельное определение типа в конструкторе `Progress` с вполне 649 | работоспособным кодом. 650 | ```php 651 | file = $file; 660 | } 661 | 662 | public function getAsPercent() 663 | { 664 | return $this->file->sent * 100 / $this->file->length; 665 | } 666 | } 667 | ``` 668 | Теперь в `Progress` можно добавить все, что только угодно. 669 | ```php 670 | artist . '/' . $this->album . '.png'; 684 | } 685 | } 686 | ``` 687 | И класс `Music` будет работать отлично. Проверить это можно на простом 688 | примере: 689 | ```php 690 | public function testItCanGetTheProgressOfAMusicStreamAsAPercent() 691 | { 692 | $music = new Music(); 693 | $music->length = 200; 694 | $music->sent = 100; 695 | 696 | $progress = new Progress($music); 697 | 698 | $this->assertEquals(50, $progress->getAsPercent()); 699 | } 700 | ``` 701 | В общем, любое измеримое содержание можно использовать вместе с классом 702 | `Progress`. Можно выразить это в переменной, изменив ее имя: 703 | ```php 704 | measurableContent = $measurableContent; 713 | } 714 | 715 | public function getAsPercent() 716 | { 717 | return $this->measurableContent->sent * 100 / $this->measurableContent->length; 718 | } 719 | 720 | } 721 | ``` 722 | Все, кажется, отлично, но в этом подходе есть одна громадная проблема. 723 | Когда `File` был указан в роли определителя типа, уверенность в том, что 724 | класс будет отменно работать, только крепчала. Это было вполне очевидно, 725 | а о каких-либо неточностях могла бы сообщить простая ошибка. 726 | 727 | Argument 1 passed to Progress::__construct() 728 | must be an instance of File, 729 | instance of Music given. 730 | 731 | Конечный результат был бы одинаковым в обоих случаях, но в первом мы 732 | получили бы сообщение. Правда, очень неясное. Нет способа узнать, что 733 | переменной (в нашем случае это - строка) не хватает каких-либо свойств 734 | или они просто не были найдены. Отладка и дебагинг в этом случае 735 | становятся проблемой: программисту приходится открывать класс `Progress` и 736 | перечитывать его для того, чтобы найти и понять проблему. Это можно 737 | уточнить по поведению `Progress`, а если точнее, то благодаря доступу к 738 | полям `sent` и `length` в методе `getAsPercent()`. Но в реальной жизни все 739 | может быть гораздо сложнее. 740 | 741 | Подобное решение нужно применять только в том случае, если ни одно из 742 | предложенных ниже решений нельзя реализовать с минимальными затратами 743 | (трудность реализации или слишком большие архитектурные изменения, 744 | которые не оправдают усилий). 745 | 746 | ### Решение 2: Стратегия паттернов проектирования 747 | 748 | Это – наиболее распространенное и наиболее доступное решение для 749 | соответствия **OCP**, простой и эффективный метод. 750 | 751 |

752 | 753 |

754 | 755 | Шаблон **Стратегия** отлично показывает используемый интерфейс. Интерфейс – 756 | это особый тип организации в объектно-ориентированном программировании, 757 | который определяет отношения между клиентом и классом сервера. Оба 758 | класса будут вести себя так, чтобы достигнуть желаемого. 759 | ```php 760 | setLength(200); 777 | $file->setSent(100); 778 | 779 | $progress = new Progress($file); 780 | 781 | $this->assertEquals(50, $progress->getAsPercent()); 782 | } 783 | ``` 784 | Как обычно, сначала пойдут примеры, где мы будем пользоваться сеттерами 785 | для установки значений. Кстати, сеттеры также можно определить в нашем 786 | измерительном интерфейсе (Measurable), но будьте внимательны с тем, что 787 | вы туда прописываете. Интерфейс для определения контракта между 788 | клиентским классом `Progress` и различными серверными классами `File` и 789 | `Music`. 790 | 791 | Нужно ли устанавливать значения для `Progress`? Вероятно, нет. Вряд ли вам 792 | придется определять сеттеры в интерфейсе, но если вы все же решите это 793 | сделать, то заставите все серверные классы работать с сеттерами. Одним 794 | из них вполне могут быть необходимы сеттеры, но другие могут вести себя 795 | неожиданно непредсказуемо. Что нужно сделать для того, чтобы `Progress` 796 | показывал температуру печи? Класс `OvenTemperature` можно инициализировать 797 | со значениями в конструкторе или же получить информацию от третьего 798 | класса. Но иметь здесь сеттеры – это странно. 799 | ```php 800 | length = $length; 813 | } 814 | 815 | public function getLength() 816 | { 817 | return $this->length; 818 | } 819 | 820 | public function setSent($sent) 821 | { 822 | $this->sent = $sent; 823 | } 824 | 825 | public function getSent() 826 | { 827 | return $this->sent; 828 | } 829 | 830 | public function getRelativePath() 831 | { 832 | return dirname($this->filename); 833 | } 834 | 835 | public function getFullPath() 836 | { 837 | return realpath($this->getRelativePath()); 838 | } 839 | } 840 | ``` 841 | Класс `File` был слегка изменен для того, чтобы соответствовать 842 | требованиям выше, и теперь он реализует интерфейс `Measurable`; в нем есть 843 | сеттеры и геттеры для полей, которые нам нужны. А класс `Music` на него 844 | очень похож. 845 | ```php 846 | measurableContent = $measurableContent; 855 | } 856 | 857 | public function getAsPercent() 858 | { 859 | return $this->measurableContent->getSent() * 100 / $this->measurableContent->getLength(); 860 | } 861 | 862 | } 863 | ``` 864 | Класс `Progress` тоже нужно немножко обновить, указав в конструкторе тип. 865 | Наш тип – это `Measurable` (измерительный). После этого у нас появляется 866 | явный контракт. В `Progress` всегда будут методы доступа – мы определили 867 | их в интерфейсе измерения. Классы `File` и `Music` всегда смогут сделать 868 | все, что нужно классу `Progress`, путем простого исполнения методов 869 | интерфейса: это необходимо, когда класс реализует интерфейс. 870 | 871 | ### Заметка об имени интерфейса 872 | 873 | Люди часто называют интерфейс с заглавной буквы или добавляют слово 874 | **«Interface»** в конце, например, `IFile` или `FileInterface`. Это обозначение 875 | старого образца и работало оно со старыми стандартами. Имя переменной 876 | или файла должно четко и ясно давать понять суть его содержимого. `IDE` 877 | определяет что-либо со скоростью в долю секунды и именно это позволяет 878 | нам сконцентрироваться на работе. 879 | 880 | Интерфейсы принадлежат их клиентам и поэтому, когда мы ходим дать имя 881 | интерфейсу, нам нужно полностью забыть о реализации и думать только о 882 | клиенте. Когда мы назвали интерфейс измеримым (Measurable), то думали о 883 | `Progress`. Если бы мы были прогрессом, что нам нужно было бы для того, 884 | чтобы переводить что-либо в проценты? Ответ более, чем прост: что-то, 885 | что можно посчитать. Поэтому мы и присвоили название `Measurable`. 886 | 887 | Не стоит забывать о том, что реализация может быть из разных областей. В 888 | нашем случае, это – музыка и файлы. Но готовый прогресс мы легко можем 889 | заново использовать в гоночном симуляторе, и тогда нашими измеряемыми 890 | классами станут скорость, количество топлива и так далее. 891 | 892 | ### Решение 3: используем шаблонный метод 893 | 894 | **Шаблонный метод** очень смахивает на стратегию, но с одним отличием: 895 | вместо интерфейса он использует абстрактные классы. Шаблонный метод 896 | рекомендуется использовать в том случае, если клиент для нашего 897 | приложения очень специфический, с небольшой возможностью повторного 898 | использования и в том случае, если у серверных классов общее поведение. 899 | 900 |

901 | 902 |

903 | 904 | ### Просмотр высокого уровня 905 | 906 | Итак, как же все это влияет на нашу архитектуру высокого уровня?  907 | 908 | Если изображение выше представляет текущую архитектуру нашего 909 | приложения, то добавление нового модуля с пятью классами (синий цвет) 910 | должно вполне ожидаемо повлиять на всю расстановку (красный цвет). 911 | 912 |

913 | 914 |

915 | 916 | В большинстве систем невозможно не повлиять на код при вводе новых 917 | классов. Но соответствие [принципу открытости-закрытости](#O) может 918 | значительно сократить классы и модули, требующие постоянного изменения. 919 | 920 | Изучая очередной новый принцип, не пытайтесь удержать в голове 921 | одновременно с ним и все остальное, иначе вы просто запутаетесь в 922 | интерфейсах для каждого класса. Такую конструкцию будет тяжело понимать 923 | и поддерживать. Наиболее оптимальным решением в этом случае станет учет 924 | возможностей и определение, будут ли здесь другие типы и серверные 925 | классы. 926 | 927 | В общем-то, можно легко представить себе новую функцию или найти ее в 928 | логах другого серверного класса. В таких случаях, интерфейс нужно 929 | добавить с самого начала. Если же вы не уверены или не можете 930 | разобраться – просто пропустите эту часть. Пусть добавлением интерфейса 931 | занимается другой программист или даже вы, но в будущем. 932 | 933 | Если вы будете все продумывать при добавлении интерфейсов, то 934 | модификаций будет мало, они будут быстрыми и легкими. Помните, если код 935 | пришлось изменить один раз, скорее всего, это нужно будет делать снова и 936 | здесь принцип открытости-закрытости может здорово помочь. 937 | 938 | SOLID «L»: Принцип подстановки Барбары Лисков (Liskov substitution principle, LSP) 939 | === 940 | 941 | **Подклассы не могут замещать поведение базовых классов.** 942 | 943 | Концепция принципа подстановки была предложена Барбарой Лисков в ее докладе на 944 | конференции 1987 года, а спустя 7 лет – опубликована в соавторстве с 945 | Джаннет Вин. Оригинальное определение принципа, предложенное Барбарой, 946 | следующее: 947 | 948 | «В том случае, если q(x) – свойство, верное по отношению к объектам х 949 | некого типа T, то свойство q(y) тоже будет верным относительно ряда 950 | объектов y, которые относятся к типу S, при этом S – подтип некого типа 951 | T.» 952 | 953 | Некоторое время спустя, после публикации Робертом С. Мартином всей 954 | пятерки принципов SOLID в книге о быстрой разработке программ, а затем и 955 | после публикации версии книги о быстрой разработке для языка 956 | программирования C\#, принцип  стал называться принципом подстановки 957 | Барбары Лисков. 958 | 959 | Это приводит нас к определению, которое дал сам Роберт С. Мартин: 960 | 961 | **Подтипы должны дополнять базовые типы.** 962 | 963 | Если это разъяснить, то получится, что подклассы должны переопределять 964 | методы базового класса так, чтобы не нарушалась функциональность с точки 965 | зрения клиента. Подробно это можно рассмотреть на простом примере: 966 | 967 | Есть существующий класс `Vehicle`, который может быть и абстрактным в том 968 | числе, и две реализации: 969 | ```php 970 | engageIgnition(); 990 | parent::startEngine(); 991 | } 992 | 993 | private function engageIgnition() 994 | { 995 | // Ignition procedure 996 | } 997 | } 998 | 999 | class ElectricBus extends Vehicle 1000 | { 1001 | public function accelerate() 1002 | { 1003 | $this->increaseVoltage(); 1004 | $this->connectIndividualEngines(); 1005 | } 1006 | 1007 | private function increaseVoltage() 1008 | { 1009 | // Electric logic 1010 | } 1011 | 1012 | private function connectIndividualEngines() 1013 | { 1014 | // Connection logic 1015 | } 1016 | 1017 | } 1018 | ``` 1019 | Клиентский класс должен иметь возможность использовать любой из них, 1020 | если он может использовать `Vehicle`. 1021 | ```php 1022 | startEngine(); 1029 | $v->accelerate(); 1030 | } 1031 | } 1032 | ``` 1033 | А это уже приводит нас к простой реализации шаблонного метода 1034 | проектирования так же, как он использовался и с принципом 1035 | открытости-закрытости. 1036 | 1037 |

1038 | 1039 |

1040 | 1041 | Основываясь на предыдущем опыте с принципом открытости-закрытости, можно 1042 | сделать вывод, что принцип Барбары Лисков сильно с ним связан. И в самом 1043 | деле, как сказал Роберт Мартин, нарушение принципа **LSP** – это скрытое 1044 | нарушение принципа **OCP**. Шаблонный метод проектирования – классический 1045 | пример соблюдения и реализации принципа подстановки, который, в свою 1046 | очередь, является одним из способов соблюдения **OCP**. 1047 | 1048 | ### Классический пример нарушения принципа LSP 1049 | 1050 | Чтобы показать нарушение как можно полнее и нагляднее, будет использован 1051 | классический понятный пример. 1052 | ```php 1053 | height = $height; 1064 | } 1065 | 1066 | public function getHeight() 1067 | { 1068 | return $this->height; 1069 | } 1070 | 1071 | public function setWidth($width) 1072 | { 1073 | $this->width = $width; 1074 | } 1075 | 1076 | public function getWidth() 1077 | { 1078 | return $this->width; 1079 | } 1080 | 1081 | } 1082 | ``` 1083 | Мы начнем с основной геометрической формы – прямоугольника (Rectangle). 1084 | Это всего лишь простой объект данных с сеттерами и геттерами для ширины 1085 | (width) и высоты (height). Если представить, что приложение уже работает 1086 | и даже на нескольких клиентах, которым нужно управлять этим 1087 | прямоугольником так, чтобы сделать из него квадрат, то придется ввести 1088 | новые функции. 1089 | 1090 | В реальной жизни, в геометрии, квадрат – это просто одна из форм 1091 | прямоугольника. Поэтому нужно попробовать реализовать класс `Square`, 1092 | расширяющий класс `Rectangle`. На первый взгляд, кажется, что подкласс – 1093 | это базовый класс, а принцип подстановки не нарушается. 1094 | 1095 |

1096 | 1097 |

1098 | 1099 | Но будет ли квадрат `Square` прямоугольником `Rectangle` уже в 1100 | программировании? 1101 | ```php 1102 | width = $value; 1109 | $this->height = $value; 1110 | } 1111 | 1112 | public public function setWidth($value) 1113 | { 1114 | $this->width = $value; 1115 | $this->height = $value; 1116 | } 1117 | } 1118 | ``` 1119 | Квадрат – это прямоугольник с одинаковой шириной и высотой, а значит, 1120 | реализация в примере выше была бы не совсем корректной. Можно было бы 1121 | переписать сеттеры, чтобы установить ширина и высоту. Но как это 1122 | повлияет на клиентский код? 1123 | ```php 1124 | setWidth(5); 1131 | $rect->setHeight(4); 1132 | 1133 | if($rect->area() != 20) { 1134 | throw new Exception('Bad area!'); 1135 | } 1136 | 1137 | return true; 1138 | } 1139 | } 1140 | ``` 1141 | Реально получить клиентский класс, который проверяет площадь 1142 | прямоугольника, и реагирует, если ее значение оказывается неправильным. 1143 | ```php 1144 | public function area() 1145 | { 1146 | return $this->width * $this->height; 1147 | } 1148 | ``` 1149 | Ну и конечно добавлен метод класса `Rectangle`. 1150 | ```php 1151 | assertTrue($client->areaVerifier($rect)); 1160 | } 1161 | 1162 | } 1163 | ``` 1164 | С помощью простого теста можно проверить работу: отправим пустой 1165 | прямоугольный объект для определения его площади. Работает. Если наш 1166 | класс `Square` определяется корректно, то его отправка на клиентский 1167 | `areaVerifier()` не повредит функциональности. В конце концов, в 1168 | математическом понимании `Square` – это все тот же `Rectangle`. Но наш ли 1169 | это класс? 1170 | ```php 1171 | public function testSquareArea() 1172 | { 1173 | $rect = new Square(); 1174 | $client = new Client(); 1175 | $this->assertTrue($client->areaVerifier($rect)); 1176 | } 1177 | ``` 1178 | Тестирование проходит легко и много времени не занимает. А уведомление 1179 | появляется при запуске теста выше. 1180 | 1181 | PHPUnit 3.7.28 by Sebastian Bergmann. 1182 | 1183 | Exception : Bad area! 1184 | #0 /paht/: /.../.../LspTest.php(18): Client->areaVerifier(Object(Square)) 1185 | #1 [internal public function]: LspTest->testSquareArea() 1186 | 1187 | Итак, в нашем «программном» смысле `Square` класс - это не `Rectangle`, 1188 | иначе бы законы геометрии и принцип подстановки Барбары Лисков 1189 | нарушались. 1190 | 1191 | Этот пример особенно хорош тем, что он показывает и нарушение **LSP**, и то, 1192 | что объектно-ориентированное программирование не может применить правила 1193 | реальной жизни к объектам. Каждый объект здесь должен быть абстракцией 1194 | над концепцией. А если мы попытаемся сопоставить реальный объект и 1195 | программный объект, то у нас никогда это не получится. 1196 | 1197 | 1198 | SOLID «I»: Принцип разделения интерфейса (interface segregation principle, ISP) 1199 | === 1200 | 1201 | Суть принципа разделения интерфейса – в бизнес-логике и клиентском 1202 | общении. Во всех модульных приложениях должен быть интерфейс, которым 1203 | может воспользоваться клиент. Это может быть классический объект, 1204 | реализуемый в шаблонах проектирования вроде `Facades`. Не важно, 1205 | применяется то или иное решение. Суть всегда остается той же: объяснить 1206 | клиентскому коду как правильно использовать модуль. Эти интерфейсы могут 1207 | находиться между различными модулями в одном приложении или проекте, или 1208 | между одним проектом в качестве сторонней библиотеки, служащей для 1209 | подачи еще одного проекта. 1210 | 1211 |

1212 | 1213 |

1214 | 1215 | Хороший способ на старте определить, что именно мы хотим реализовать в 1216 | нашем модуле. Подобное начало может привести к одной из реализаций: 1217 | 1218 | 1. большой класс `Car` или `Bus` реализует методы интерфейса `Vehicle`. Одни 1219 | размеры таких классов советуют избегать их любой ценой; 1220 | 2. маленькие классы вроде `LightsControl`, `SpeedControl` или `RadioCD` 1221 | реализуют весь интерфейс, но делают что-то полезное только для 1222 | реализуемых ими частей; 1223 | 1224 | Очевидно, что ни одно из этих решений не подходит для реализации нашей 1225 | бизнес-логики. 1226 | 1227 |

1228 | 1229 |

1230 | 1231 | Мы могли бы попробовать еще один подход: разбить интерфейс на куски, 1232 | каждый из которых займется своей реализацией. Для маленьких классов – 1233 | идеально. Объекты, реализующие интерфейсы, будут использовать другой тип 1234 | транспорта, например, машину (car) на картинке выше. 1235 | 1236 |

1237 | 1238 |

1239 | 1240 | Но это может фундаментально изменить наше восприятие архитектуры. Вместо 1241 | реализации `Car` становится клиентом. А мы хотим дать возможность клиенту 1242 | использовать весь модуль. 1243 | 1244 |

1245 | 1246 |

1247 | 1248 | Предположим, что проблема реализации уже решена, а бизнес-логика – 1249 | стабильна. Прежде всего, нужно обеспечить единый интерфейс со всеми 1250 | реализациями и пусть клиенты `BusStation`, `HighWay`, `Driver` используют все, 1251 | что угодно из реализации. Это перекладывает ответственность за поведение 1252 | на клиентов (подобный метод часто применялся в старых приложениях). 1253 | [Принцип разделения интерфейса](#I) утверждает, что ни один клиент не должен 1254 | зависеть от неиспользуемых методов. 1255 | 1256 | Но есть одна проблема: все клиенты зависят от всех методов. Разве 1257 | `BusStation` должен зависеть от радиостанции, которую выбрал водитель, или 1258 | от фар автобуса? Нет. Но что, если так оно и будет? Нужно вспомнить 1259 | [принцип единой обязанности](#S). Если `BusStation` зависит от многих реализаций 1260 | (и даже не используемых), то он может потребовать изменений, если 1261 | изменится одна из реализаций. Такого быть не должно. 1262 | 1263 | Интерфейсы относятся к клиентам, а не к реализациям, поэтому и создавать 1264 | их нужно в лучших отношениях с клиентом. Мы должны разбить интерфейсы на 1265 | кусочки так, чтобы они лучше работали с клиентами. 1266 | 1267 |

1268 | 1269 |

1270 | 1271 | Конечно, могут появиться дубляжи, но помните, что интерфейсы – это 1272 | простые определения имен функций и логика в них не реализуется, а 1273 | значит, и проблем с дублированием не будет. 1274 | 1275 | Здесь есть преимущество перед клиентами, которые зависят только от того, 1276 | что им нужно и что они используют сами по себе. В некоторых случаях 1277 | клиенты могут использовать и нуждаться в нескольких интерфейсах, и это 1278 | совершенно нормально, пока они используют все методы всех интерфейсов, 1279 | от которых зависят. 1280 | 1281 | Еще один приятный момент заключается в том, что в нашей бизнес-логике 1282 | один класс может реализовать несколько интерфейсов, если ему это 1283 | понадобится. Так что мы можем обеспечить единую реализацию для всех 1284 | общих методов между интерфейсами. Интерфейсы также заставляют нас думать 1285 | о нашем коде больше с точки зрения клиента, что, в свою очередь, 1286 | приводит нас к уже более легкому тестированию. Так что мы не только 1287 | сделали наш код лучше для наших клиентов, мы также сделали его проще для 1288 | себя, чтобы легче понимать, тестировать и реализовывать. 1289 | 1290 | ### Резюме 1291 | 1292 | [Принцип подстановки Барбары 1293 | Лисков](#L) демонстрирует, 1294 | почему реальные объекты нельзя сопоставлять один к одному с объектами 1295 | программирования, и учит писать код таким образом, чтоб подтипы хорошо 1296 | уживались с базовыми типами. Принцип подстановки дополняет другие 1297 | принципы SOLID и проще интерпретируется в их контексте. 1298 | 1299 | Принцип разделения интерфейса учит нас уважать клиентов еще сильнее, чем 1300 | мы это делали раньше. Внимание к их потребностям может сделать наш код 1301 | намного лучше, а нашу работу – проще и интереснее. 1302 | 1303 | 1304 | SOLID «D»: Принцип инверсии зависимостей (dependency inversion principle, DIP) 1305 | === 1306 | 1307 | Принцип [единственной 1308 | обязанности](#S), [открытости-закрытости](#O), [подстановки](#L), [разделения 1309 | интерфейсов](#I) и [инверсии 1310 | зависимостей](#D) – пятерка принципов, на которые следует ориентироваться при 1311 | написании кода. 1312 | 1313 | Хотя говорить о преобладающей важности одного из принципов будет не 1314 | верно, но **отметить влияние принципа инверсии зависимостей на код 1315 | нужно обязательно**. Если вы заметили, что другие принципы трудно понять 1316 | или применить, начните с этого, а остальные применяйте уже к коду, 1317 | соответствующему **DIP** 1318 | 1319 | ### Определение 1320 | 1321 | 1. Модули высокого уровня не должны зависеть от модулей низкого уровня. 1322 | Оба должны зависеть от абстракций. 1323 | 2. Абстракции не должны зависеть от деталей. Детали должны зависеть от 1324 | абстракций. 1325 | 1326 | Этот принцип выделил Роберт С. Мартин в своей книге о быстрой разработке 1327 | программ, а затем переиздал в версии книги для языка C\#. Принцип 1328 | инверсии зависимостей - последний из пятерки принципов SOLID. 1329 | 1330 | ### DIP в реальном мире 1331 | 1332 | Если вы программируете не очень аккуратно, не совсем разбираетесь в 1333 | правилах программирования и не соблюдаете ряд принципов SOLID, в своей 1334 | работе вам, возможно, придется пройти все 7 кругов ада, ощутить все на 1335 | собственном опыте, прежде чем код станет действительно хорошим. 1336 | 1337 | Принципы SOLID - это исключительно архитектурные принципы Роберта С. 1338 | Мартина, которые полностью меняют правила игры, весь ход 1339 | программирования. Далее мы проиллюстрируем влияние нескольких 1340 | архитектурных решений, которые появились благодаря принципу **DIP**, и 1341 | серьезно повлияли на один из наших проектов. 1342 | 1343 | Большинство веб-проектов включают в себя три основных технологии: `HTML`, 1344 | `PHP` и `SQL`. Определенная версия каждого из этих приложений, о которых мы 1345 | говорим, или то, какой тип реализаций `SQL` вы используете – все это не 1346 | имеет абсолютно никакого значения в нашем случае. Дело в том, что 1347 | информация из `HTML` формы должна заканчиваться в базе данных. Остальное 1348 | обеспечит `РНР`. 1349 | 1350 | Из этого следует уяснить одну важную вещь - хороший способ, которым эти 1351 | три технологии представляют три различных архитектурных слоя: 1352 | 1353 | 1. пользовательский интерфейс, 1354 | 2. бизнес-логику, 1355 | 3. долговременное сохранение данных. 1356 | 1357 | Скоро мы поговорим об этих слоях. А сейчас давайте сосредоточимся на 1358 | некоторых странных, но часто встречающихся решениях, применяемых для 1359 | того, чтобы заставить технологии работать вместе. 1360 | 1361 | Есть много проектов, которые используют `SQL` код в тегах `PHP` внутри 1362 | `HTML`-файла. Или `PHP`-код отражает и страницы `HTML`, и непосредственно 1363 | интерпретирует глобальные переменные `$_GET` или `$_POST`. Казалось бы, 1364 | все хорошо. Но в чем же тогда проблема? 1365 | 1366 |

1367 | 1368 |

1369 | 1370 | На изображении выше показана сырая версия того, что мы описывали в 1371 | предыдущем абзаце. Стрелки указывают на различные зависимости, и мы 1372 | можем сделать вывод, что все базируется на всем, все зависит от всего. 1373 | Если нам придется изменить таблицу базы данных, скорее всего, в конечном 1374 | итоге, мы закончим редактированием `HTML`-файла. Или если мы изменим поле 1375 | в `HTML`, то закончим изменением столбца в операторе `SQL`. Или, как видно 1376 | из второй схемы, нам действительно придется сильно изменить наш `PHP`, 1377 | если изменится `HTML`. А в худшем случае, если мы генерируем весь 1378 | `HTML`-контент из `PHP`-файла, скорее всего, нам придется менять `PHP`, чтобы 1379 | изменить `HTML`-контент. Поэтому нет никаких сомнений в том, что 1380 | зависимости просто лавируют между нашими классами и модулями. Но на этом 1381 | все не заканчивается: вы можете хранить операции или код на `PHP` в 1382 | таблицах `SQL`. 1383 | 1384 |

1385 | 1386 |

1387 | 1388 | На схеме выше видно, что запросы к базе данных `SQL` возвращают `PHP`-код, 1389 | сгенерированный с данными из таблиц. Эти `PHP`-е функции или классы 1390 | создают другие `SQL`-запросы, которые возвращают уже другой `PHP`-код. Этот 1391 | цикл будет продолжаться до тех пор, пока вся информация не будет 1392 | получена и возвращена, вероятно, пользовательскому интерфейсу. 1393 | 1394 | Многим это покажется полной чушью, но если вы еще не работали с 1395 | проектом, созданным и реализованным по этому сценарию, то в будущем, 1396 | скорее всего, вам придется с этим столкнуться. Большинство существующих 1397 | проектов, независимо от используемых языков программирования, были 1398 | написаны программистами старой закалки, которым было все равно или они 1399 | не знали, как можно сделать код лучше. Если вы сейчас читаете этот 1400 | мануал о принципах программирования, значит, вам нужно научиться 1401 | программировать на порядок лучше, чем вы можете на сегодняшний день. Это 1402 | значит, что вы **уважаете** или даже только начинаете уважать свою 1403 | профессию, **хотите понять свое ремесло и сделать его лучше**. 1404 | 1405 | Еще одна версия может рассказать нам об ошибках, сделанных нашими 1406 | предшественниками, и последствиями от них. В некоторых проектах вы 1407 | можете получить, в итоге, практически неподдерживаемое состояние из-за 1408 | их старой и кросс-зависимой архитектуры. В итоге, вам придется просто 1409 | отказаться от таких проектов навсегда, и тогда вы поймете, что больше не 1410 | хотите повторять эти ошибки снова. Поэтому стремитесь делать чистую 1411 | архитектуру, которая будет соответствовать принципам SOLID, во-первых, а 1412 | во-вторых, принципу инверсии зависимостей. 1413 | 1414 |

1415 | 1416 |

1417 | 1418 | В этой архитектуре есть несколько интересных моментов: 1419 | 1420 | 1. пользовательский интерфейс (в большинстве случаев, веб-фреймворк 1421 | `MVC`) или любой другой механизм доставки в нашем проекте будет 1422 | зависеть от бизнес-логики. Бизнес-логика сама по себе довольно 1423 | абстрактна, а пользовательский интерфейс – само воплощение 1424 | конкретики. Он представляет собой одну из деталей проекта, и к тому 1425 | же, очень нестабильную. Ничто не должно зависеть от 1426 | пользовательского интерфейса, ничто не должно зависеть от 1427 | веб-фреймворка `MVC`; 1428 | 2. еще одно интересное наблюдение, сделанное нами, говорит, что 1429 | долговременное сохранение, база данных, наш `MySQL` или `PostgreSQL` 1430 | базируются на бизнес-логике. Это позволяет менять сохраняемость так, 1431 | как это нужно нам. Если завтра нам понадобится изменить `MySQL` вместе 1432 | с `PostgreSQL` или просто текстовые файлы, мы легко сможем это 1433 | сделать. Нам, конечно, придется реализовать определенный уровень 1434 | сохраняемости для новых методов сохранения состояния, но для этого 1435 | не придется менять отдельные строки кода в нашей бизнес-логике; 1436 | 3. В конце концов, в правой части нашей бизнес-логики, вне ее, у нас 1437 | есть все классы, которые создают классы бизнес-логики. Эти классы 1438 | созданы как точки входа в наши приложения. Многие люди склонны 1439 | думать, что они принадлежат к бизнес-логике, но делают они это 1440 | только для того, чтобы создать бизнес-объекты. Они – просто классы, 1441 | которые помогают нам создавать другие классы. Бизнес-объекты и 1442 | логика, которую они обеспечивают, не зависят от них. Мы могли бы 1443 | использовать различные модели или создать простой объект, чтобы 1444 | обеспечить бизнес-логику. Это не имеет значения. После того, как 1445 | бизнес-объекты созданы, они начинают выполнять свою работу. 1446 | 1447 | ### Приступим к коду 1448 | 1449 | Соблюдать [принцип инверсии зависимостей](#D) (DIP) на архитектурном уровне 1450 | довольно легко, если вы соблюдаете классические шаблоны проектирования. 1451 | Использовать и показать это внутри бизнес-логики достаточно просто, а 1452 | местами - даже весело. Представим себе приложение для чтения электронных 1453 | книг. 1454 | ```php 1455 | assertRegExp('/pdf book/', $reader->read()); 1465 | } 1466 | } 1467 | 1468 | class PDFReader 1469 | { 1470 | private $book; 1471 | 1472 | public function __construct(PDFBook $book) 1473 | { 1474 | $this->book = $book; 1475 | } 1476 | 1477 | public function read() 1478 | { 1479 | return $this->book->read(); 1480 | } 1481 | 1482 | } 1483 | 1484 | class PDFBook 1485 | { 1486 | public function read() 1487 | { 1488 | return "reading a pdf book."; 1489 | } 1490 | } 1491 | ``` 1492 | Мы начали разработку электронной читалки как читалки `PDF`. Пока не 1493 | возникло никаких проблем. У нас есть класс `PDFReader`, который использует 1494 | `PDFBook`. Функция `read()` в читалке относится к методу `read()`. В этом мы 1495 | убедимся путем регулярной проверки выражений после ключевого элемента 1496 | строки, которые возвращает метод PDFBook's reader(). 1497 | 1498 | Не забывайте о том, что это всего лишь пример. Мы не станем 1499 | реализовывать логику чтения `PDF`-файлов или любых других форматов файлов. 1500 | Наши тесты – проверки на некоторых базовых строках. Если бы мы писали 1501 | настоящее приложение, единственная разница заключалась бы в способе 1502 | тестирования разных файловых форматов. Структура зависимости была бы 1503 | очень похожа на предложенную в примере. 1504 | 1505 |

1506 | 1507 |

1508 | 1509 | Использование читалки формата `PDF`, которая использует книгу в формате 1510 | `PDF`, может быть вполне здравым решением в ряде ограниченных случаев. 1511 | Если наша задача заключалась в том, чтобы написать только читалку 1512 | формата `PDF`, то такое решение фактически соответствует задаче. Но мы 1513 | хотели написать универсальную читалку, которая использовала бы несколько 1514 | разных форматов, в том числе, и уже реализованную `PDF`-версию. 1515 | Переименуем класс нашей читалки. 1516 | ```php 1517 | assertRegExp('/pdf book/', $r->read()); 1527 | } 1528 | } 1529 | 1530 | class EBookReader 1531 | { 1532 | private $book; 1533 | 1534 | public function __construct(PDFBook $book) 1535 | { 1536 | $this->book = $book; 1537 | } 1538 | 1539 | public function read() 1540 | { 1541 | return $this->book->read(); 1542 | } 1543 | } 1544 | 1545 | class PDFBook 1546 | { 1547 | public function read() 1548 | { 1549 | return "reading a pdf book."; 1550 | } 1551 | } 1552 | ``` 1553 | Переименование не дало каких-либо функциональных эффектов. Тест пройден 1554 | на «отлично». 1555 | 1556 | Testing started at 1:04 PM ... 1557 | PHPUnit 3.7.28 1558 | Time: 13 ms, Memory: 2.50Mb 1559 | OK (1 test, 1 assertion) 1560 | Process finished with exit code 0 1561 | 1562 |

1563 | 1564 |

1565 | 1566 | Наша читалка уже стала гораздо абстрактнее. Намного универсальнее. 1567 | Сейчас у нас есть универсальный `EBookReader`, который может читать 1568 | довольно специфический формат книг – `PDFBook`. Наша абстракция зависит от 1569 | деталей. То, что наша книга находится в формате `PDF` – все лишь деталь, 1570 | от которой ничего не должно зависеть. 1571 | ```php 1572 | assertRegExp('/pdf book/', $reader->read()); 1582 | } 1583 | 1584 | } 1585 | 1586 | interface EBook 1587 | { 1588 | public function read(); 1589 | } 1590 | 1591 | class EBookReader 1592 | { 1593 | private $book; 1594 | 1595 | public function __construct(EBook $book) 1596 | { 1597 | $this->book = $book; 1598 | } 1599 | 1600 | public function read() 1601 | { 1602 | return $this->book->read(); 1603 | } 1604 | 1605 | } 1606 | 1607 | class PDFBook implements EBook 1608 | { 1609 | public function read() 1610 | { 1611 | return "reading a pdf book."; 1612 | } 1613 | } 1614 | ``` 1615 | Наиболее популярным и часто используемым решением для того, чтобы 1616 | инвертировать зависимость, является введение более абстрактного модуля в 1617 | наш проект. Наиболее абстрактным элементом в ООП является интерфейс. 1618 | Таким образом, любой другой класс может зависеть от интерфейса, и все 1619 | еще соблюдать **DIP**. 1620 | 1621 | Мы создали интерфейс для нашей читалки, который назвали `EBookReader`, и 1622 | который отражает все потребности `EBook`. Это – прямой результат 1623 | соблюдения [принципа разделения интерфейса](#I), который основывается на идее, 1624 | что интерфейсы должны отражать потребности клиентов. Интерфейсы 1625 | относятся к клиентам и называются таким образом, чтобы отражать типы и 1626 | объекты, которые необходимы клиентам. Интерфейсы должны содержать 1627 | методы, которые клиенты хотят использовать. Для `EBookReader` вполне 1628 | естественно использовать `EBook` и содержать метод `read()`. 1629 | 1630 |

1631 | 1632 |

1633 | 1634 | Теперь вместо единственной зависимости, у нас их становится две. 1635 | 1636 | 1. Первая зависимость указывает от `EBookReader` к интерфейсу `EBook` и на 1637 | используемый тип. `EBookReader` использует `EBook`. 1638 | 2. Вторая зависимость уже несколько другого рода. Она указывает от 1639 | `PDFBook` к тому же интерфейсу `EBook`, но на реализуемый тип. `PDFBook` – 1640 | это просто особая форма `EBook`, которая реализует интерфейс для того, 1641 | чтобы удовлетворить потребности клиентов. 1642 | 1643 | Неудивительно, ведь такое решение позволит нам просматривать в нашей 1644 | читалке различные типы электронных книг. Единственное условие для всех 1645 | этих книг – они должны соответствовать интерфейсу `EBook` и реализовывать 1646 | его. 1647 | ```php 1648 | assertRegExp('/pdf book/', $reader->read()); 1658 | } 1659 | 1660 | public function testItCanReadAMobiBook() 1661 | { 1662 | $book = new MobiBook(); 1663 | $reader = new EBookReader($book); 1664 | 1665 | $this->assertRegExp('/mobi book/', $reader->read()); 1666 | } 1667 | 1668 | } 1669 | 1670 | interface EBook 1671 | { 1672 | public function read(); 1673 | } 1674 | 1675 | class EBookReader 1676 | { 1677 | private $book; 1678 | 1679 | public function __construct(EBook $book) 1680 | { 1681 | $this->book = $book; 1682 | } 1683 | 1684 | public function read() 1685 | { 1686 | return $this->book->read(); 1687 | } 1688 | 1689 | } 1690 | 1691 | class PDFBook implements EBook 1692 | { 1693 | public function read() 1694 | { 1695 | return "reading a pdf book."; 1696 | } 1697 | } 1698 | 1699 | class MobiBook implements EBook 1700 | { 1701 | public function read() 1702 | { 1703 | return "reading a mobi book."; 1704 | } 1705 | } 1706 | ``` 1707 | Все это приводит нас к принципу [открытости-закрытости](#O), и круг 1708 | замыкается. [Принцип инверсии зависимостей](#D) помогает нам соблюдать все 1709 | остальные принципы из пятерки SOLID. Соблюдая принцип **DIP**, мы 1710 | практически начинаем соблюдать **OCP**, можем [разделять обязанности](#S), 1711 | [правильно используем подтипы](#L) и можем [разделять интерфейсы](#I). 1712 | 1713 | ### Финальные мысли 1714 | 1715 | Ну, вот и все, мы наконец-то закончили и полностью разобрали все мануалы 1716 | о принципах SOLID. Это должно полностью изменить ваши представления об 1717 | архитектуре и сделать вашу работу проще и интереснее. Мы должны 1718 | стремиться делать наш код лучше, используя эту пятерку. 1719 | 1720 | В объектно-ориентированном программировании пятерка принципов SOLID – 1721 | один из важнейших стержней, которые должны делать код лучше, а жизнь 1722 | программистов - проще. 1723 | --------------------------------------------------------------------------------