Принципы SOLID

SOLID — мнемонический акроним, введённый Майклом Фэзерсом(Michael Feathers) для первых пяти принципов, названных Робертом Мартином в начале 2000-х, которые означали пять основных принципов объектно-ориентированного программирования и проектирования.

Для чего нужны принципы SOLID?

Принципы SOLID — это набор правил, которые надо применять во время работы над программным обеспечением(ПО) для его улучшения.

Удивительно то, что принципы были сформулированы пару десятков лет назад и до сих пор актуальны. Это может говорить только о их эффективности.

S – The Single Responsibility Principle(Принцип единственной ответственности)

Каждый класс выполняет лишь одну задачу.

Принципы SOLID
Принцип единственной ответственности

Очень простой, но в тоже время и важный принцип. Надо следить в ПО, чтобы каждый класс или интерфейс не был перегружен лишней логикой и выполнял только одну задачу.

Необходимо понимать, что каждое изменение в логике работы класса влечет за собой изменения в коде. Если класс имеет в себя более одной ответственности, то изменения его при изменении бизнес-логики будут происходить чаще. Так же класс взаимодействует с большим числом иных классов, что сильнее их связывает между собой и становится сложнее в поддержке.

Часто при правке багов и хотфиксов нарушается этот принцип. В случае хотфиксов необходимо занести это в технический долг и выполнить его в ближайшее время.

Пример

Есть класс Order в котором описана логика работы с заказом.

class Order {public function calculateTotalSum() {/*...*/}public function getItems() {/*...*/}public function getItemCount() {/*...*/}public function addItem( $item) {/*...*/}public function deleteItem( $item) {/*...*/}public function printOrder() {/*...*/}public function showOrder() {/*...*/}public function load() {/*...*/}public function save() {/*...*/}public function update() {/*...*/}public function delete() {/*...*/}}

Надо разделить его на пару классов и выделить логику работы с базой и выведением в отдельные классы.

class Order {public function calculateTotalSum() {/*...*/}public function getItems() {/*...*/}public function getItemCount() {/*...*/}public function addItem( $item) {/*...*/}public function deleteItem( $item) {/*...*/}}class OrderRepository {public function load( $orderID) {/*...*/}public function save( $order) {/*...*/}public function update( $order) {/*...*/}public function delete( $order) {/*...*/}}class OrderViewer {public function printOrder( $order) {/*...*/}public function showOrder( $order) {/*...*/}}

O – The Open Closed Principle(Принцип открытости/закрытости)

Классы должны быть открыты для расширения и закрыты для модификации.

Принципы SOLID
Принцип открытости/закрытости

В идеальном мире это для добавление нового функционала необходимо добавлять новый код, а не изменять старый.

Багфикс, рефакторинг и улучшение производительности это не нарушение этого принципа. Принцип гласит про изменение логики работы ПО.

Пример

У нас есть класс OrderRepository. В его способе load описана работа получения заказа из БД.

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) {/*...*/}}

Когда появляется необходимость приобретать заказы не только с базы, а к примеру с API, то можно без труда поступить следующим образом:

  1. Создать интерфейс IOrderSource
  2. Сделать 2 класса MySQLOrderSource и ApiOrderSource, которые выполняют данный интерфейс
  3. И передавать в конструктор класса OrderRepository инстанс, который реализует интерфейс IOrderSource.

Таким образом мы можем без проблем добавлять новый источник заказов просто реализовав класс с интерфейсом IOrderSource.

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 OrderRepository {private $source;public function __constructor( IOrderSource $source ) {$this->source = $source;}public function load( $orderID ) {return $this->source->load( $orderID );}public function save( $order ) {/*...*/}public function update( $order ) {/*...*/}}

L – The Liskov Substitution Principle (Принцип подстановки Барбары Лисков)

Наследники должны повторять поведение родительского класса и должны вести себя без сюрпризов.

Принципы SOLID
Принцип подстановки Барбары Лисков

Пример

У нас есть класс LessonRepository, который в способе getAll возвращает массив всех уроков из файла. Появилась необходимость приобретать уроки из БД. Создаем класс DatabaseLessonRepository, наследуем его от LessonRepository и переписываем способ getAll.

class LessonRepository { //return array of lesson through file system.public function getAll() {return $files;}}class DatabaseLessonRepository extends LessonRepository { //return a Collection type instead of arraypublic function getAll() {return Lesson::all();}}

В способе getAll у класса DatabaseLessonRepository вместо коллекции мы должны вернуть массив.

interface LessonRepositoryInterface {public function getAll(): array;}class FilesystemLessonRepository implements LessonRepositoryInterface {public function getAll(): array {return $files;}}class DatabaseLessonRepository implements LessonRepositoryInterface {public function getAll(): array {return Lesson::all()->toArray();}}

I – The Interface Segregation Principle (Принцип разделения интерфейса)

Много мелких интерфейсов лучше, чем один большой.

Принципы SOLID
Принцип разделения интерфейса

Пример

У нас есть интерфейс Bird, который имеет в себя способы eat и fly. Когда в коде появляется Penguin, который не умеет летать необходимо бросить Exception.

interface Bird {public function eat();public function fly();}class Duck implements Bird {public function eat() {/*...*/}public function fly() {/*...*/}}class Penguin implements Bird {public function eat() {/*...*/}public function fly() {/* exception */}}

Вместо Exception лучше разделить интерфейсы для птицы на Bird, FlyingBird и RunningBird.

interface Bird {public function eat();}interface FlyingBird {public function fly();}interface RunningBird {public function run();}class Duck implements Bird, FlyingBird {public function eat() {/*...*/}public function fly() {/*...*/}}class Penguin implements Bird, RunningBird {public function eat() {/*...*/}public function run() {/*...*/}}

D – The Dependency Inversion Principle (Принцип инверсии зависимостей)

Зависимость на абстракциях, нет зависимостей на что-то конкретное.

Принципы SOLID
Принцип инверсии зависимостей

Самое простое решение начать применять этот принцип это писать тесты т.к. при их написании тестов надо мокать какие-то данные и как итог: проще переписать класс, чем написать на него тест.

Пример

У нас есть класс EBookReader, который принимает в коструктор объект класса PDFBook и в способе read — читает его. Когда появляется необходимость читать не только из PDF-файла класс надо изменять.

class EBookReader {private $book;public function __construct( PDFBook $book ) {$this->book = $book;}public function read() {return $this->book->read();}}class PDFBook {public function read() { /*...*/}}

Лучше сделать интерфейс EBook с способом read. И тогда в EBookReader мы сможем передавать любые объекты, которые реализовывают интерфейс EBook.

interface EBook {public function read();}class EBookReader {private $book;public function __construct( EBook $book ) {$this->book = $book;}public function read() {return $this->book->read();}}class PDFBook implements EBook {public function read() {/*...*/}}class MobiBook implements EBook {public function read() {/*...*/}}

Основные триггеры того, что принципы SOLID нарушены:

  • Оператор switch
  • Большое кол-во констант
  • new внутри способов класса
  • instanceof

Часть проблем решает применение паттернов проектирования:

Найболее полулярные паттерны в PHP:

  • Strategy
  • State
  • Chain of Responsibility
  • Visitor
  • Decorator
  • Composition
  • Factory

Ссылки

  • Оригинал статьи SOLID Principles от Максима Денисенко .

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *