5 принципов SOLID на примерах языка Php

В этом посте вы увидите примеры 5 принципов SOLID. Они пригодятся, если вы захотите узнать больше о грамотном объектно-ориентированном программировании.

Введение

Что значит быть хорошим программистом?

Если вы — любитель спорта в Европе, вы наверняка знаете, что Криштиану Роналду в настоящее время является лучшим футболистом в мире (по крайней мере, в тройке лучших).

Если вы — американец и любите автогонки, вы наверняка знаете Ричарда Петти и Джимми Джонсона. Для азиатской аудитории может быть известно имя Йокозуна Хакухо.

Вы увидели несколько примеров, но такие знания не определяют программистов.

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

Я встречал технических директоров, для которых многолетний опыт работы на определенном языке не очень важен. Они предпочитают soft skills.

Другие будут оценивать по количеству функций PHP, которые вы знаете наизусть.

Компании с ленивым менеджментом проверят ваши навыки с помощью автоматизированного онлайн-тестирования навыков (которые, на мой взгляд, являются просто мошенничеством и на самом деле вообще не проверяют никаких навыков).

Как веб-разработчик, за свою карьеру я сталкивался с десятками разработчиков и видел тысячи строк кода.

И когда мне нужно оценить навыки разработчика, я в основном смотрю на два фактора.

Насколько легко читать их код и насколько вероятно, что их код будет работать и масштабироваться через год.

К счастью, есть некоторые основы или принципы, которые позволяют легко стать лучше в программировании.

Принципы SOLID были изобретены Робертом К. Мартином. Он — инженер-программист и преподаватель. Он был одним из авторов Agile Manifesto и выступал за разработку нескольких принципов проектирования программного обеспечения, включенных в SOLID.

SOLID — это набор из 5 принципов, направленных на то, чтобы сделать ваш код более читабельным и простым для рефакторинга.

Мистеру Мартину пришла в голову эта идея в конце 90-х. Большинство принципов, которые Мартин продвигал в течение многих лет, были изобретены им самим.

Как бы то ни было, принцип замещения Лисков и принцип открытого-закрытого, которые вы увидите ниже, были разработаны Барбарой Лисков и Бертраном Мейером.

Любопытно, но аббревиатура SOLID была на самом деле введена Майклом Фезерсом, а не мистером Мартином.

Цель SOLID — помочь вам создать код, который будет хорошо работать в долгосрочной перспективе, а не только после следующего развертывания в рабочей среде.

Причина, по которой SOLID чрезвычайно важен в PHP, заключается в том, что этот язык является языком, основанным на классах, поэтому использование принципов SOLID приведет к улучшению кода.

«Конечная цель вашего кода — работать и быть понятным»

Почему SOLID?

Есть несколько причин, по которым SOLID является хорошим показателем для измерения «красоты» кода в веб-приложении. Все они имеют отношение к плохому PHP, а не к добру самих принципов SOLID.

Код спагетти

Спагетти, несомненно, самый известный вид макарон в мире, древнее секонд-хэнд блюдо из кухни Бахавалпура, которое попало в Италию благодаря знаменитому исследователю Марко Поло и быстро стало любимой едой среди населения.

Почти каждый рецепт, включающий спагетти (с мясом, овощами или морепродуктами), невероятно прост в приготовлении.

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

Поскольку очень сложно найти начало и конец одной и той же нити пасты, с веб-приложением, имеющем спагетти-код, очень сложно работать.

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

Жесткость

Сколько раз вы писали несколько строк где-то в своем приложении, после чего обнаруживалось, что ваш код не работает?

Это то, что Роберт С. Мартин называет жесткостью (ригидностью).

Есть способы решить эту проблему с помощью unit и функциональных тестов. Но лучший способ — в корне проблемы, это, когда вы пишете код.

Каждая часть вашего кода не должна быть жестко связана с другими частями. 

Если одно изменение затрагивает другие части кода, для вас есть единственное ключевое слово. Рефакторинг!

Хрупкость

Хрупкость и жесткость связаны. Оба вызваны новым кодом, вставленным в приложение, и оба имеют одинаковые симптомы, проявляющиеся в ошибках и предупреждениях.

А вот хрупкость обнаружить сложнее. Иногда бывает так, что вы развернули какой-то код, а о проблемах узнали через несколько дней. 

Причина лежит в несвязанных ошибках в двух разных частях приложения. Вот что называется хрупкостью.

Неподвижность

Последняя проблема, которую мы часто видим в объектно-ориентированном проекте — неподвижность.

У вас возникает неподвижность, когда вы испытываете то чувство дежавю, то ощущение, что вы уже писали такую ​​же фичу или сниппет раньше. И, когда вы пытаетесь повторно использовать старый код вместо написания нового, он не работает.

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

Прежде чем погрузиться в принципы SOLID, убедитесь, что вы знаете основы языка PHP, чтобы иметь четкое представление о приведенных ниже примерах.

Как писать код по принципам SOLID?

Начнем с предупреждения: написание кода в стиле SOLID означает написание большего количества кода, чем то, к которому вы привыкли.

Если вы пишете с использованием SOLID, вы потратите больше времени на написание кода для создания структуры вашего веб-приложения.

Хорошая новость заключается в том, что в долгосрочной перспективе вам потребуется половина времени на чтение и попытки понять написанный вами код.

Итак, давайте начнем с 5 принципов SOLID:

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

Принцип единой ответственности (Single-responsibility principle, SRP) утверждает, что: у класса должна быть только одна причина для изменения.

Чтобы понять это, сделайте небольшое упражнение. Посмотрите на последний класс PHP, над которым вы работали, и запишите предложение, определяющее его роль в веб-приложении.

Сделали?

Если вы использовали спряжение «и» или вы использовали «или», пришло время рефакторинга. Этот класс не использует первый принцип SOLID.

Каждый класс должен отвечать только за одну функцию, а ваш метод одновременно обрабатывает только одну задачу.

Давайте посмотрим на этот пример:

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

Он делает это…

… плюс он связывается с базой данных, выполняющей запрос, чем отправляет электронное письмо пользователю.

Слишком много всего для одного метода. Как мы можем улучшить его?

Что изменилось?

Начнем с класса. Теперь мы определили два класса, когда мы создаем экземпляр объекта. И эти два класса включают свою собственную логику, один отвечает за сохранение данных в базе данных, другой за отправку электронных писем.

Что касается метода регистрации, то теперь он намного чище и легче читается. На самом деле он написан простым английским языком. Вам не понадобится даже 2 секунды, чтобы понять, что делает этот метод.

Единственная задача класса — зарегистрировать пользователя, общение с базой данных и отправка почты — это не его дело, да и не должно быть.

Принцип открытия-закрытия

(Open-closed principle) Программные сущности должны быть открыты для расширения, но закрыты для модификации.

Разберем предложение:

Программные сущности в данном случае являются нашими классами.

Они должны быть открыты для расширения. Хитря, мы можем расширить поведение наших классов в PHP, используя ключевое слово extends. Значит, принцип открытого-закрытого относится к наследованию?

Solid — это не про PHP, это про написание программ. И некоторые языки программирования не могут расширять свои классы. Некоторые из них даже не имеют классов. 

Принцип открытости-закрытости касается не наследования, а композиции строительного блока в объектно-ориентированном дизайне.

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

В этом коде у нас есть абстрактный класс с некоторыми методами, которые определяют, как утки плавают, крякают и летают. И мы распространяем поведение этого класса на другие классы.

Классы определенного вида уток, например, прирученных и диких работают нормально до того момента, пока мы не придем к созданию экземпляров из резины и дерева.

Как вы можете себе представить, поведение этих двух классов совершенно разное. Они не купаются. Они только всплывают. Они не крякают (хорошо, резиновые пищат при нажатии). И они определенно не летают.

Это распространенная проблема, и одна из причин споров об использовании композиции вместо наследования.

Как мы можем перевести этот код и заставить его использовать композицию вместо наследования?

Мы начинаем с другого плана. На этот раз мы создаем интерфейсы для каждой из функций, которые должны быть у утки (обратите внимание, что на этот раз мы говорим, что утка «имеет», а не утка «есть»).

В конце концов, в нашем классе мы можем передавать интерфейсы в конструктор и использовать параметр в методах класса.

Чтобы объяснить, в чем преимущество всего этого и как соблюсти принцип открытого-закрытого, мы теперь добавим еще одну функцию к нашим уткам.

Скажем, вместо того, чтобы крякать, мы хотим, чтобы наши любимые резиновые уточки пищали.

Теперь мы создаем экземпляр RubberDuck и передаем контроллеру новый класс Squeaking, мы просто изменили его поведение, теперь метод quack будет давать другой результат, без фактического изменения кода внутри класса.

То, что вы видели, похоже на поведение шаблона проектирования, называемого шаблоном стратегии.

Другой способ реализовать это — использовать другой известный и довольно простой шаблон проектирования — шаблон проектирования Decorator.

Принцип подстановки Лисков

(Liskov substitution principle) Объект в программе должен заменяться экземплярами его подтипов без изменения корректности этой программы.

Интересна история Барбары Лисков, родившейся в Лос-Анджелесе в 1939 году. Она закончила Стэнфорд и получила степень доктора философии компьютерных наук в мире, где ее курс обучения был предназначен преимущественно для мужского большинства.

Она стала одной из первых в Америке, получивших эту степень, а позже начала свою карьеру c преподавания на факультете компьютерных наук в Массачусетском технологическом институте.

Она известна принципом, который она разработала вместе с Джанетт Винг.

Официальный принцип гласит:

Пусть Φ(x) — доказуемое свойство объектов x типа T. Тогда Φ(y) должно быть истинным для объектов y типа S, где S — подтип T.

Как видите, объяснение довольно простое (вам просто нужна степень в области математики или ракетостроения).

Шутки в сторону, давайте разберем это предложение и посмотрим, что оно на самом деле означает.

Цель этого принципа — позволить вам решить, можете ли вы использовать наследование в своем проекте или вы должны предпочесть композицию.

Чтобы сделать вещи более понятными, держите пример с уткой:

T — класс утки Duck

S — это класс дикой утки WildDuck, который наследуется от класса утки.

Пусть Φ(x) — доказуемое свойство об объектах x типа Duck. Тогда Φ(y) должно быть истинным для объектов y типа WildDuck, где WildDuck является подтипом Duck.

Здесь уже есть небольшие улучшения, но мы можем добиться большего.

Пусть y — Дональд, утка, которая ведет дикую жизнь в пруду.

x — это Дейзи, еще одна утка. Она — экземпляр Duck, не дикая и не домашняя.

Φ — способность Дональда плавать (представлена ​​в PHP как метод).

Пусть способность плавать является доказуемым свойством объектов Дейзи типа Duck. Тогда плавание должно быть истинным для объектов Дональда типа WildDuck, где WildDuck является подтипом Duck.

Вам нужно подумать об этом предложении, когда вы создаете наследование в своем коде.

Всегда ли оно верно?

В этом случае ответ — нет.

Что, если через год мы создадим Даки — резиновую утку, которая должна вписаться в наше программное обеспечение?

Теперь принцип уже не верен. Даки не умеет плавать. Это сигнал о том, что в этом случае вам нужно предпочесть композицию наследованию.

Эта концепция тесно связана с концепцией Design by Contract. Советую ознакомиться с ней, чтобы все стало понятнее .

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

(Interface segregation principle) Множество клиентских интерфейсов лучше, чем один интерфейс общего назначения.

Этот принцип был впервые разработан г-ном Робертом Мартином, когда он был инженером-программистом в Xerox.

У них был интерфейс под названием job, и этот интерфейс отвечает за десятки различных задач.

Через некоторое время это повлекло за собой несколько проблем, которые он хотел решить.

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

Ниже приведен реальный пример. И мы увидим, как реализовать фильтры на странице поиска в соответствии с принципом разделения интерфейсов.

Первый и более простой способ сделать эту реализацию — иметь где-то в коде интерфейс Filter с методом, позволяющим создавать запрос Query в соответствии с запросом Request.

Затем мы реализуем этот фильтр в нашем классе текстового поля. Проверим, установлено ли значение параметра, а затем возвращаем запрос, который хочет пользователь.

Легко.

Что будет, если пользователь нашего приложения будет искать строку «0»?

Возможно, вы знаете, что PHP расценивает 0 как false. Поэтому код с if в нашем классе Text не будет выполняться, и мы просто вернем запрос как есть.

Это баг. Исправим!

Способ решить эту проблему — создать интерфейс SearchParameters. У него есть два метода.

Метод has(), который возвращает логическое значение в соответствии с результатом проверки, которую мы видели в предыдущем примере. И метод value(), который может вернуть строку исключения.

Когда пришло время выполнить наше действие, мы вводим запрос, который будет сохранен конструктором, затем оцениваем запрос с помощью метода has и возвращаем соответствующее значение.

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

Это в принципе, просто английский язык.

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

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

(Dependency inversion principle) Зависьте от абстракции, а не от конкретизации (конкретики).

Чтобы понять это, нам нужно определить, что такое абстракция и конкретизация.

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

Примером является наш класс Duck ранее.

Конкретизация — это настоящие классы, экземпляры которых мы можем получить из WildDuck, RubberDuck и т. д.

Будем предполагать, что вы знакомы с внедрением зависимостей (dependency injection) и внедрением конструктора (constructor injection).

Если нет, вот краткое объяснение внедрения зависимостей.

Что ж, инверсия зависимостей гласит, что вы должны инвертировать зависимости.

Переверните отношения таким образом, что поначалу это может показаться неестественным.

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

Давайте реализуем инверсию зависимостей в одном из наших предыдущих примеров.

Это почти введение в ООП. И код работает нормально. Ведь эти методы возвращают только строку.

Прошел год с того момента, как вы развернули этот код на продакшене. И теперь нам нужно иметь гораздо больше типов плавательного поведения с более сложным кодом.

В соответствии с первыми четырьмя принципами, которые вы только что открыли, мы могли бы создать разные классы, отвечающие за вычисление результата для каждого типа уток.

Теперь этот код немного сложнее понять.

Здесь у нас есть две иерархии, но ими легче управлять. И мы можем добавить в эти классы поведения Behaviour столько логики, сколько захотим.

Зависьте от абстракций, а не от конкретики.

Снова взглянув на этот принцип, мы понимаем, что происходит что-то неправильное. Мы делаем прямо противоположное тому, что должны. Конкретные классы уток сочетаются с поведением низкого уровня.

Чтобы решить эту проблему, нам нужно инвертировать инъекции. И, как вы увидите, нам больше не понадобятся нижние классы.

Вот так.

Мы исправили код. Мы соблюдаем этот принцип SOLID. И, самое главное — теперь наш код надежен в долгосрочной перспективе.

Смысл принципа инверсии зависимостей в том, что НАМ НЕ НУЖНО создавать экземпляры подклассов. Во многих случаях достаточно внедрения зависимостей. И нам не нужны иерархии.

Кроме того, наш класс Duck теперь заботится только о SwimmingBehaviour и ничего не знает о типах WildDuckSwimmingBehaviour или RubberDuckSwimmingBehaviour.

Вывод

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

Умные программисты придумывают теории каждый день, но лишь немногие держатся и все же подтверждают свои выводы спустя годы.

Принципы SOLID — один из них.

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

Мы должны предпочесть композицию наследованию.

Мы должны использовать определенные типы и избегать использования массива, когда он не нужен, и особенно набрасывать на него что-либо.

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

Надеюсь, после прочтения вы внедрите эти принципы в свой код и начнете считать себя хорошим программистом.

Этот пост основан на выступлении Гарета Эллиса на конференции PHP UK в 2017 году.

Статья переведена с оригинала.

Щелкните ниже, чтобы поставить оценку!
Всего: 0 В среднем: 0

Написать комментарий

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