что такое solid принципы

Принципы SOLID в картинках

Если вы знакомы с объектно-ориентированным программированием, то наверняка слышали и о принципах SOLID. Эти пять правил разработки ПО задают траекторию, по которой нужно следовать, когда пишешь программы, чтобы их проще было масштабировать и поддерживать. Они получили известность благодаря программисту Роберту Мартину.

В Сети множество отличных статей, где рассказывается о принципах SOLID, но иллюстрированных среди них мне практически не попадалось. Из-за этого таким людям со склонностью к визуальному восприятию информации – таким, как я – бывает сложно схватывать суть и не отвлекаться.

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

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

Принципы SOLID

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

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

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

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

O — Open-Closed (Принцип открытости-закрытости)

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

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

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

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

Если П является подтипом Т, то любые объекты типа Т, присутствующие в программе, могут заменяться объектами типа П без негативных последствий для функциональности программы.


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

Если у вас имеется класс и вы создаете на его базе другой класс, исходный класс становится родителем, а новый – его потомком. Класс-потомок должен производить такие же операции, как и класс-родитель. Это называется наследственностью.

Необходимо, чтобы класс-потомок был способен обрабатывать те же запросы, что и родитель, и выдавать тот же результат. Или же результат может отличаться, но при этом относиться к тому же типу. На картинке это показано так: класс-родитель подаёт кофе (в любых видах), значит, для класса-потомка приемлемо подавать капучино (разновидность кофе), но неприемлемо подавать воду.

Если класс-потомок не удовлетворяет этим требованиям, значит, он слишком сильно отличается от родителя и нарушает принцип.

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

I — Interface Segregation (Принцип разделения интерфейсов)

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

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

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

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

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

Модули верхнего уровня не должны зависеть от модулей нижнего уровня. И те, и другие должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.


Для начала объясню термины, которые здесь применяются, простыми словами.

Модули (или классы) верхнего уровня = классы, которые выполняют операцию при помощи инструмента
Модули (или классы) нижнего уровня = инструменты, которые нужны для выполнения операций
Абстракции – представляют интерфейс, соединяющий два класса
Детали = специфические характеристики работы инструмента

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

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

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

Источник

Простое объяснение принципов SOLID

Принципы SOLID — это стандарт программирования, который все разработчики должны хорошо понимать, чтобы избегать создания плохой архитектуры. Этот стандарт широко используется в ООП. Если применять его правильно, он делает код более расширяемым, логичным и читабельным. Когда разработчик создаёт приложение, руководствуясь плохой архитектурой, код получается негибким, даже небольшие изменения в нём могут привести к багам. Поэтому нужно следовать принципам SOLID.

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

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

Я буду объяснять SOLID самым простым способом, так что новичкам легче будет разобраться. Будем рассматривать принципы один за другим.

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

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

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

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

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

Код после рефакторинга будет выглядеть так:

Принцип открытости/закрытости (Open-closed Principle)

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

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

Читайте также:  что значит большая е в примерах

Как же можно решить поставленную задачу? Смотрите:

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

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

Функции, использующие указатели ссылок на базовые классы, должны уметь использовать объекты производных классов, даже не зная об этом.

Попросту говоря: подкласс/производный класс должен быть взаимозаменяем с базовым/родительским классом.

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

В этом коде нарушен обсуждаемый принцип и показан способ исправления:

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

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

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

Как и в случае с принципом единственной ответственности, цель принципа разделения интерфейса заключается в минимизации побочных эффектов и повторов за счёт разделения ПО на независимые части.

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

Высокоуровневые модули не должны зависеть от низкоуровневых. Оба вида модулей должны зависеть от абстракций.

Абстракции не должны зависеть от подробностей. Подробности должны зависеть от абстракций.

Проще говоря: зависьте от абстракций, а не от чего-то конкретного.

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

Есть распространённое заблуждение, что «инверсия зависимостей» является синонимом «внедрения зависимостей». Но это разные вещи.

Класс PasswordReminder должен зависеть от абстракций, а не от чего-то конкретного. Но как это сделать? Рассмотрим пример:

Источник

Почему SOLID – важная составляющая мышления программиста. Разбираемся на примерах с кодом

Привет! Меня зовут Иван, я сотрудничаю со львовским офисом EPAM как Solution Architect, а карьеру в IT начал 10 лет назад. За это время заметил, что многие любят работать на проектах, которые начинаются с нуля. Однако не всем удается построить систему, которую будет все еще легко поддерживать и развивать спустя год.

Вполне естественно, что вместе с разрастанием системы будет повышаться и ее сложность. Успех разработки такой системы будет зависеть от того, насколько хорошо вы держите под контролем ее сложность. Для достижения этой цели существуют дизайн-паттерны, лучшие практики, а главное – принципы проектирования, такие как SOLID, GRASP и DDD.

В данной статье я хочу акцентировать внимание на том, что SOLID – важная составляющая мышления разработчика, которую нужно развивать и тренировать.

Я покажу несколько примеров с кодом, где нарушаются принципы SOLID. Мы выясним, к каким последствиям это может привести в долгосрочной перспективе и как это можно исправить. На мой взгляд, статья будет интересна как back-end, так и front-end разработчикам разных уровней.

Зачем нужен SOLID

SOLID – набор принципов объектно-ориентированного программирования, который представил Роберт Мартин в 1995 году. Их идея состоит в необходимости избегать зависимостей между компонентами кода. Код с большим количеством зависимостей (т.н. «спагетти-код») поддерживать сложно. Основные проблемы такого кода:

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

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

Например, класс User. Его ответственность – предоставлять информацию о пользователе: имя, e-mail и тип подписки, которую он использует в сервисе.

Рассмотрим метод hasUnlimitedContentAccess. На основе типа подписки он определяет есть ли у пользователя неограниченный доступ к контенту. Но разве делать такой вывод – ответственность класcа User?

Получается, что у класса User есть две цели существования: предоставлять информацию о пользователе и делать вывод об уровне доступа к контенту на основе подписки. Это нарушает принцип Single Responsibility.

Почему существование метода hasUnlimitedContentAccess в классе User имеет негативные последствия? Потому что контроль за типом подписки «расплывается» по всей программе. Кроме класса User могут быть классы MediaLibrary и Player, которые на основе этих же данных тоже будут решать, что им делать. Каждый класс трактует значение типа подписки по-своему. Если правила имеющихся подписок изменятся, необходимо будет обновить все классы, так как каждый выстроил свой набор правил работы с ними.

Удалим метод hasUnlimitedContentAccess в классе User и создадим новый класс, который будет отвечать за работу с подписками.

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

Single Responsibility Principle касается не только уровня классов – модули классов также необходимо проектировать таким образом, чтобы они были узкоспециализированы.

Кроме SOLID существует и другой набор принципов проектирования программного обеспечения – GRASP. Некоторые его принципы пересекаются с SOLID. Если же говорить о Single Responsibility Principle, то с GRASP можно сопоставить:

Приницип открытости/закрытости (Open/Close Principle)

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

Наверное, каждый из нас видел бесконечные цепочки if then else или switch. Как только добавляется очередное условие, мы пишем очередной if then else, меняя при этом сам класс. Либо класс выполняет процесс с множеством последовательных шагов – и каждый новый шаг приводит к его изменению. А это нарушает Open/Close Principle.

Рассмотрим несколько способов расширения класса без его непосредственного изменения.

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

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

А что делать, если мы не можем добавлять методы к фигурам? Существуют методы, которые противоречат Single Responsibility Principle. Тогда можно воспользоваться шаблоном проектирования «Стратегия» (Strategy): создать множество похожих алгоритмов и вызывать их по определенному ключу.

Читайте также:  что значит использовать кириллицу при регистрации в навигаторе

Кроме того, стратегии можно регистрировать в Inversion of Control контейнере. В этом случае класс, нуждающийся в них, получит стратегии на этапе создания автоматически.

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

Используем дизайн-паттер «Конвейер» (Pipeline)

Теперь, если необходимо изменить способ обработки изображения, мы модицифируем массив с методами. Сам класс ImageProcessor остается неизменным. Представьте, что есть необходимость обрабатывать разные изображения по-разному. Вместо того, чтобы писать разные версии ImageProcessor, иначе скомбинируем нужные нам методы в массиве pipeMethods.

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

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

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

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

Принципы GRASP, общие с Open/Close Principle:

Принцип подстановки Лисков (Liskov Substitution Principle)

Если объект базового класса заменить объектом его производного класса, то программа должна продолжить работать корректно.

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

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

Рассмотрим следующую иерархию транспортных средств:

Все работает до момента, когда мы добавляем новый класс – Поезд.

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

Чтобы исправить ситуацию, мы можем добавить два родительских класса: FreeDirectionalVehicle – будет разрешить произвольное направление движения и BidirectionalVehicle — движение только вперед и назад. Теперь все классы будут наследовать лишь те методы, которые смогут обеспечить.

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

В этой иерархии мы не сможем легко заменить объекты родительского класса Logger объектами TcpLogger, т.к. до и после вызова метода нам необходимо дополнительно вызвать openConnection и closeConnection. Получается, мы накладываем 2 дополнительных условия на вызов метода log, что также нарушает принцип подстановки Лисков.

Чтобы решить ситуацию выше, мы можем сделать методы openConnection и closeConnection приватными. В методе log класса TcpLogger организуем запись логов в файл. С периодичностью (например, каждую минуту) будем открывать соединение, отправлять файл с логами и закрывать соединение. Дополнительно необходимо убедиться, что прежде, чем программа будет закрыта, мы отправили все логи. Если программа была завершена аварийно, можем отправить логи во время следующего ее запуска.

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

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

Так как с файла мы читаем локально, метод Connect лишний. Разделим общий интерфейс IDataSource:

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

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

Данный принцип состоит из двух утверждений:

Наш модуль верхнего уровня UserService использует детали реализации трех модулей нижнего уровня: localStorage, fetch и Date. Такой подход плох тем, что если мы, например, вместо fetch решим использовать библиотеку, которая делает HTTP-запросы, то придется переписывать UserService. Кроме того, такой код сложно покрыть тестами.

Еще одним нарушением является то, что из метода getUser мы возвращаем реализованный класс User, а не его абстракцию – интерфейс IUser.

Создадим абстракции, с которыми было бы удобно работать внутри модуля UserService.

Как видим, код стал гораздо проще, его легко тестировать. Теперь взглянем на реализацию интерфейсов ICache и IRemoteService.

Мы сделали враперы над localStorage и fetch. Важным моментом в реализации двух классов является то, что мы не используем localStorage и fetch напрямую. Мы все время работаем с созданными для них интерфейсами. LocalStorage и fetch будут передаваться в конструктор, если там не будет указано никаких параметров. Для тестов же можно создать mocks или stubs, которые заменят localStorage или fetch, и передать их как параметры в конструктор.

Похожий прием используют и для даты: если ничего не передать, то каждый раз LocalStorageCache будет получать новую дату. Если же для тестов необходимо зафиксировать определенную дату, передавать ее нужно в параметре конструктора.

Выводы

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

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

И в заключение: отца-основателя SOLID, Роберта Мартина, небезосновательно считают настоящей рок-звездой в мире разработки ПО. На его книгах уже выросло не одно поколение суперуспешных программистов. «Clean Code» и «Clean Coder» — две его книги о том, как писать качественный код и соответствовать высочайшим стандартам в индустрии. Думаю, многие из вас уже успели причитать хотя бы одну из них, а если нет, то у вас есть неплохой шанс прокачать свой уровень разработчика.

Источник

Принципы SOLID, о которых должен знать каждый разработчик

Объектно-ориентированное программирование принесло в разработку ПО новые подходы к проектированию приложений. В частности, ООП позволило программистам комбинировать сущности, объединённые некоей общей целью или функционалом, в отдельных классах, рассчитанных на решение самостоятельных задач и независимых от других частей приложения. Однако само по себе применение ООП не означает, что разработчик застрахован от возможности создания непонятного, запутанного кода, который тяжело поддерживать. Роберт Мартин, для того, чтобы помочь всем желающим разрабатывать качественные ООП-приложения, разработал пять принципов объектно-ориентированного программирования и проектирования, говоря о которых, с подачи Майкла Фэзерса, используют акроним SOLID.

Читайте также:  что делать когда кот сильно линяет

Материал, перевод которого мы сегодня публикуем, посвящён основам SOLID и предназначен для начинающих разработчиков.

Что такое SOLID?

Вот как расшифровывается акроним SOLID:

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

«Одно поручение. Всего одно.» — Локи говорит Скурджу в фильме «Тор: Рагнарёк».
Каждый класс должен решать лишь одну задачу.

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

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

Например, рассмотрим этот код:

Как такая структура класса может привести к проблемам?

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

Для того чтобы привести вышеприведённый код в соответствие с принципом единственной ответственности, создадим ещё один класс, единственной задачей которого является работа с хранилищем, в частности — сохранение в нём объектов класса Animal :

Вот что по этому поводу говорит Стив Фентон: «Проектируя классы, мы должны стремиться к тому, чтобы объединять родственные компоненты, то есть такие, изменения в которых происходят по одним и тем же причинам. Нам следует стараться разделять компоненты, изменения в которых вызывают различные причины».

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

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

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

Самая главная проблема такой архитектуры заключается в том, что функция определяет то, какой звук издаёт то или иное животное, анализируя конкретные объекты. Функция AnimalSound не соответствует принципу открытости-закрытости, так как, например, при появлении новых видов животных, нам, для того, чтобы с её помощью можно было бы узнавать звуки, издаваемые ими, придётся её изменить.

Добавим в массив новый элемент:

После этого нам придётся поменять код функции AnimalSound :

Как привести функцию AnimalSound в соответствие с принципом открытости-закрытости? Например — так:

Если теперь добавить в массив объект, описывающий новое животное, функцию AnimalSound менять не придётся. Мы привели её в соответствие с принципом открытости-закрытости.

Рассмотрим ещё один пример.

Представим, что у нас есть магазин. Мы даём клиентам скидку в 20%, используя такой класс:

Теперь решено разделить клиентов на две группы. Любимым ( fav ) клиентам даётся скидка в 20%, а VIP-клиентам ( vip ) — удвоенная скидка, то есть — 40%. Для того, чтобы реализовать эту логику, было решено модифицировать класс следующим образом:

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

Если решено дать скидку в 80% «супер-VIP» клиентам, выглядеть это должно так:

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

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

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

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

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

Для того чтобы эта функция не нарушала принцип подстановки, преобразуем её с использованием требований, сформулированных Стивом Фентоном. Они заключаются в том, что методы, принимающие или возвращающие значения с типом некоего суперкласса ( Animal в нашем случае) должны также принимать и возвращать значения, типами которых являются его подклассы ( Pigeon ).

Вооружившись этими соображениями мы можем переделать функцию AnimalLegCount :

Теперь в классе Animal должен появиться метод LegCount :

А его подклассам нужно реализовать этот метод:

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

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

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

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

Рассмотрим интерфейс Shape :

Он описывает методы для рисования кругов ( drawCircle ), квадратов ( drawSquare ) и прямоугольников ( drawRectangle ). В результате классы, реализующие этот интерфейс и представляющие отдельные геометрические фигуры, такие, как круг (Circle), квадрат (Square) и прямоугольник (Rectangle), должны содержать реализацию всех этих методов. Выглядит это так:

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

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

Теперь интерфейс ICircle используется лишь для рисования кругов, равно как и другие специализированные интерфейсы — для рисования других фигур. Интерфейс Shape может применяться в качестве универсального интерфейса.

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

Объектом зависимости должна быть абстракция, а не что-то конкретное.

Здесь класс Http представляет собой высокоуровневый компонент, а XMLHttpService — низкоуровневый. Такая архитектура нарушает пункт A принципа инверсии зависимостей: «Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций».

Класс Http не должен знать о том, что именно используется для организации сетевого соединения. Поэтому мы создадим интерфейс Connection :

Интерфейс Connection содержит описание метода request и мы передаём классу Http аргумент типа Connection :

Перепишем класс XMLHttpService таким образом, чтобы он реализовывал этот интерфейс:

В результате мы можем создать множество классов, реализующих интерфейс Connection и подходящих для использования в классе Http для организации обмена данными по сети:

Итоги

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

Уважаемые читатели! Используете ли вы принципы SOLID в своих проектах?

Источник

Строительный портал