Solid паттерн: SOLID — принципы объектно-ориентированного программирования

SOLID / Хабр

SOLID критикует тот, кто думает, что действительно понимает ООП

© Куряшкин Виктор

Я знаком с принципами SOLID уже 6 лет, но только в последний год осознал, что они означают. В этой статье я дам простое объяснение этим принципам. Расскажу о минимальных требованиях к языку программирования для их реализации. Дам ссылки на материалы, которые помогли мне разобраться.

Первоисточники

Придумал принципы SOLID Роберт Мартин (Uncle Bob). Естественно, что в своих работах он освещает эту тему.

Книга “Принципы, паттерны и методики гибкой разработки на языке C#” 2011 года. Большинство статей, которые я видел, основываются именно на этой книге. К сожалению, она дает расплывчатое описание принципов, что сильно ударило по их популярности.

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

Книга “Clean Architecture” 2017 года. Описывает архитектуру, построенную из кирпичиков, удовлетворяющих SOLID принципам. Дает определение структурному, объектно-ориентированному, функциональному программированию. Содержит лучшее описание SOLID принципов, которое я когда-либо видел.

Требования

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

Полиморфизм дает возможность для разных типов использовать один код.

Полиморфизм можно грубо разделить на динамический и статический.

  • Динамический полиморфизм — это про абстрактные классы, интерфейсы, утиную типизацию, т.е. только в рантайме будет понятно, с каким типом будет работать наш код.
  • Статический полиморфизм — это в основном про шаблоны (genererics). Когда уже на этапе компиляции из одного шаблонного кода генерируется код специфичный для каждого используемого типа.

Кроме привычных языков вроде Java, C#, Ruby, JavaScript, динамический полиморфизм реализован, например в

  • Golang, с помощью интерфейсов
  • Clojure, с помощью протоколов и мультиметодов
  • в прочих, совсем не “ООП” языках

Принципы

SOLID принципы советуют, как проектировать модули, т. е. кирпичики, из которых строится приложение. Цель принципов — проектировать модули, которые:

  • способствуют изменениям
  • легко понимаемы
  • повторно используемы

A module should be responsible to one, and only one, actor.

Старая формулировка: A module should have one, and only one, reason to change.

Часто ее трактовали следующим образом: Модуль должен иметь только одну обязанность. И это главное заблуждение при знакомстве с принципами. Все несколько хитрее.

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

OCP: The Open Closed Principle

A software artifact should be open for extension but closed for modification.

Старая формулировка: You should be able to extend a classes behavior, without modifying it.

Это определенно может ввести в ступор. Как можно расширить поведение класса без его модификации? В текущей формулировке Роберт Мартин оперирует понятием артефакт, т.е. jar, dll, gem, npm package. Чтобы расширить поведение, нужно воспользоваться динамическим полиморфизмом.

Например, наше приложение должно отправлять уведомления. Используя dependency inversion, наш модуль объявляет только интерфейс отправки уведомлений, но не реализацию. Таким образом, логика нашего приложения содержится в одном dll файле, а класс отправки уведомлений, реализующий интерфейс — в другом. Таким образом, мы можем без изменения (перекомпиляции) модуля с логикой использовать различные способы отправки уведомлений.

Этот принцип тесно связан с LSP и DIP, которые мы рассмотрим далее.

LSP: The Liskov Substitution Principle

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

Классический пример нарушения. Есть базовый класс Stack, реализующий следующий интерфейс: length, push, pop. И есть потомок DoubleStack, который дублирует добавляемые элементы. Естественно, класс DoubleStack нельзя использовать вместо Stack.

У этого принципа есть забавное следствие: Объекты, моделирующие сущности, не обязаны реализовывать отношения этих сущностей. Например, у нас есть целые и вещественные числа, причем целые числа — подмножество вещественных. Однако, double состоит из двух int: мантисы и экспоненты. Если бы int наследовал от double, то получилась бы забавная картина: родитель содержит 2-х своих детей.

В качестве второго примера можно привести Generics. Допустим, есть базовый класс Shape и его потомки Circle и Rectangle. И есть некая функция Foo(List<Shape> list). Мы считаем, что List<Circle> можно привести к List<Shape>. Однако, это не так. Допустим, это приведение возможно, но тогда в list можно добавить любую фигуру, например rectangle. А изначально list должен содержать только объекты класса Circle.

ISP: The Interface Segregation Principle

Make fine grained interfaces that are client specific.

Под интерфейсом здесь понимается именно Java, C# интерфейс. Разделение интерфейса облегчает использование и тестирование модулей.

DIP: The Dependency Inversion Principle

Depend on abstractions, not on concretions.

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

Что такое модули верхних уровней? Как определить этот уровень? Как оказалось, все очень просто. Чем ближе модуль к вводу/выводу, тем ниже уровень модуля. Т.е. модули, работающие с BD, интерфейсом пользователя, низкого уровня. А модули, реализующие бизнес-логику — высокого уровня.

Что такое зависимость модулей? Это ссылка на модуль в исходном коде, т. е. import, require и т.п. С помощью динамического полиморфизма в runtime можно обратить эту зависимость.

Есть модуль Logic, реализующий логику, который должен отсылать уведомления. В этом же пакете объявляется интерфейс ISender, который используется Logic. Уровнем ниже, в другом пакете объявляется ConcreteSender, реализующий ISender. Получается, что в момент компиляции Logic не зависит от ConcreteSender. В runtime, например, через конструктор в Logic устанавливается экземпляр ConcreteSender.

Отдельно стоит отметить частый вопрос “Зачем плодить абстракции, если мы не собираемся заменять базу данных?”.

Логика тут следующая. На старте проекта, мы знаем, что будем использовать реляционную базу данных, и это точно будет Postgresql, а для поиска — ElasticSearch. Мы даже не планируем их менять в будущем. Но мы хотим отложить принятие решений о том, какая будет схема таблиц, какие будут индексы, и т.п. до момента, пока это не станет проблемой. И на этот момент мы будем обладать достаточной информацией, чтобы принять правильное решение. Также мы можем раньше отладить логику нашего приложения, реализовать интерфейс, собрать обратную связь от заказчика, и минимизировать последующие изменения, ведь многое реализовано только в виде заглушек.

Принципы SOLID подходят для проектов, разрабатываемых по гибким методологиям, ведь Роберт Мартин — один из авторов Agile Manifesto.

Принципы SOLID стремятся свести изменение модулей к их добавлению и удалению.

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

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

Еще больше полезной информации для программистов вы найдете на нашем сайте.

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

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

  • S: Single Responsibility Principle (Принцип единственной ответственности).
  • O: Open-Closed Principle (Принцип открытости-закрытости).
  • L: Liskov Substitution Principle (Принцип подстановки Барбары Лисков).
  • I: Interface Segregation Principle (Принцип разделения интерфейса).
  • D: Dependency Inversion Principle (Принцип инверсии зависимостей).

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

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

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

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

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

Класс Animal, представленный здесь, описывает какое-то животное. Этот класс нарушает принцип единственной ответственности. Как именно нарушается этот принцип?

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

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

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

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

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

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

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

Продолжим работу над классом Animal.

Мы хотим перебрать список животных, каждое из которых представлено объектом класса Animal, и узнать о том, какие звуки они издают. Представим, что мы решаем эту задачу с помощью функции AnimalSounds:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Теперь эта функция не интересуется типами передаваемых ей объектов. Она просто вызывает их методы LegCount. Всё, что она знает о типах — это то, что обрабатываемые ей объекты должны принадлежать классу Animal или его подклассам.

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

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

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

Теперь функции AnimalLegCount не нужно знать о том, объект какого именно подкласса класса Animalона обрабатывает для того, чтобы узнать сведения о количестве конечностей у животного, представленного этим объектом. Функция просто вызывает метод LegCount класса Animal, так как подклассы этого класса должны реализовывать этот метод для того, чтобы их можно было бы использовать вместо него, не нарушая правильность работы программы.

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

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

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

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

Странный у нас получился код. Например, класс Rectangle, представляющий прямоугольник, реализует методы (drawCircle и drawSquare), которые ему совершенно не нужны. То же самое можно заметить и при анализе кода двух других классов.

Предположим, мы решим добавить в интерфейс Shape ещё один метод, drawTriangle, предназначенный для рисования треугольников:

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

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

Принцип разделения интерфейса предостерегает нас от создания интерфейсов, подобных Shape из нашего примера. Клиенты (у нас это классы Circle, Square и Rectangle) не должны реализовывать методы, которые им не нужно использовать. Кроме того, этот принцип указывает на то, что интерфейс должен решать лишь какую-то одну задачу (в этом он похож на принцип единственной ответственности), поэтому всё, что выходит за рамки этой задачи, должно быть вынесено в другой интерфейс или интерфейсы.

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

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

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

  1. Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
  2. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

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

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

Класс Http вынужденно зависит от класса XMLHttpService. Если мы решим изменить механизм, используемый классом Http для взаимодействия с сетью — скажем, это будет Node. js-сервис или, например, сервис-заглушка, применяемый для целей тестирования, нам придётся отредактировать все экземпляры класса Http, изменив соответствующий код. Это нарушает принцип открытости-закрытости.

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

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

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

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

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

Как можно заметить, здесь высокоуровневые и низкоуровневые модули зависят от абстракций. Класс Http (высокоуровневый модуль) зависит от интерфейса Connection (абстракция). Классы XMLHttpService, NodeHttpService и MockHttpService (низкоуровневые модули) также зависят от интерфейса Connection.

Кроме того, стоит отметить, что следуя принципу инверсии зависимостей, мы соблюдаем и принцип подстановки Барбары Лисков. А именно, оказывается, что типы XMLHttpService, NodeHttpService и MockHttpService могут служить заменой базовому типу Connection.

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

Еще больше полезной информации для программистов вы найдете на нашем сайте.

Принцип SOLID в программировании: понять на примерах из жизни

  • Читать
  • Обсуждать
  • Практика
  • Видео
  • Курсы
  • Улучшить статью

    Сохранить статью

    В разработке программного обеспечения Объектно-ориентированный дизайн играет решающую роль, когда речь идет о написании гибкого, масштабируемого, поддерживаемого и многократно используемого кода. Существует так много преимуществ использования OOD, но каждый разработчик должен также знать принцип SOLID для хорошего объектно-ориентированного проектирования в программировании. Принцип SOLID был введен Robert C. Martin , также известный как Uncle Bob , является стандартом кодирования в программировании. Этот принцип является аббревиатурой пяти принципов, которые приведены ниже…

    1. Принцип единой ответственности (SRP)
    2. Принцип открытой/закрытой
    3. Принцип замещения Лискова (LSP)
    4. Принцип разделения интерфейса (ISP)
    5. Принцип инверсии зависимостей (DIP)

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

    1. Принцип единой ответственности: Этот принцип гласит, что « у класса должна быть только одна причина для изменения », что означает, что у каждого класса должна быть одна обязанность, одна работа или одна цель. Возьмем пример разработки программного обеспечения. Задача разделена на разных участников, выполняющих разные задачи: дизайнеры интерфейса занимаются дизайном, тестировщик занимается тестированием, а разработчик бэкенда занимается частью разработки бэкэнда, тогда мы можем сказать, что у каждого есть одна работа или ответственность.
    В большинстве случаев, когда программистам нужно добавить функции или новое поведение, они внедряют все в существующий класс, что совершенно неправильно. Это делает их код длинным, сложным и отнимает время, когда позже что-то нужно изменить. Используйте слоев в своем приложении и разбейте классы Бога на более мелкие классы или модули.

    2. Принцип открытости/закрытости: Этот принцип гласит, что « программные объекты (классы, модули, функции и т. д.) должны быть открыты для расширения, но закрыты для модификации », что означает, что вы должны иметь возможность расширять поведение класса, не изменяя его.
    Предположим, что разработчику A необходимо выпустить обновление для библиотеки или платформы, а разработчик B хочет изменить или добавить какую-либо функцию, тогда разработчику B разрешено расширять существующий класс, созданный разработчиком A, но разработчик B не должен изменять класс напрямую. Использование этого принципа позволяет отделить существующий код от модифицированного, что обеспечивает лучшую стабильность, удобство сопровождения и сводит к минимуму изменения, как в вашем коде.

    3. Принцип замены Лисков: Принцип был введен Барбарой Лисков в 1987 году, и в соответствии с этим принципом «Производные или дочерние классы должны быть заменяемыми для своих базовых или родительских классов ». Этот принцип гарантирует, что любой класс, который является дочерним по отношению к родительскому классу, можно использовать вместо своего родителя без какого-либо неожиданного поведения.
    Вы можете понять это так, что сын фермера должен унаследовать навыки земледелия от своего отца и должен быть в состоянии заменить своего отца, если это необходимо. Если сын хочет стать фермером, то он может заменить своего отца, но если он хочет стать игроком в крикет, то, безусловно, сын не может заменить своего отца, даже если они оба принадлежат к одной и той же семейной иерархии.
    Одним из классических примеров этого принципа является прямоугольник с четырьмя сторонами. Высота прямоугольника может быть любым значением, а ширина может быть любым значением. Квадрат – это прямоугольник с одинаковой шириной и высотой. Таким образом, мы можем сказать, что можем расширить свойства класса прямоугольника до класса квадрата. Для этого вам нужно поменять местами дочерний (квадратный) класс с родительским (прямоугольным) классом, чтобы он соответствовал определению квадрата с четырьмя равными сторонами, но производный класс не влияет на поведение родительского класса, поэтому, если вы сделаете что это нарушит принцип подстановки Лисков. Проверьте ссылку Принцип замещения Лисков для лучшего понимания.

    4. Принцип разделения интерфейсов: Этот принцип является первым принципом, который применяется к интерфейсам вместо классов в SOLID, и он аналогичен принципу единой ответственности. В нем говорится, что « не заставляют клиентов реализовывать интерфейс, который не имеет к ним отношения ». Здесь ваша главная цель — сосредоточиться на том, чтобы избежать толстого интерфейса и отдать предпочтение множеству небольших клиентских интерфейсов. Вы должны предпочесть множество клиентских интерфейсов, а не один общий интерфейс, и каждый интерфейс должен иметь определенную ответственность.
    Предположим, вы входите в ресторан и являетесь чистым вегетарианцем. Официант в этом ресторане дал вам карту меню, которая включает вегетарианские и невегетарианские блюда, напитки и сладости. В этом случае, как у клиента, у вас должна быть карточка меню, которая включает только вегетарианские блюда, а не все, что вы не едите в своей еде. Здесь меню должно быть разным для разных типов клиентов. Карту общего или общего меню для всех можно разделить на несколько карт вместо одной. Использование этого принципа помогает уменьшить побочные эффекты и частоту необходимых изменений.

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

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

    В приведенных выше строках просто говорится, что если высокоуровневый модуль или класс будет больше зависеть от низкоуровневых модулей или классов, тогда ваш код будет иметь тесную связь, и если вы попытаетесь внести изменения в один класс, это может нарушить работу другого класса. что является рискованным на уровне производства. Поэтому всегда старайтесь делать классы как можно более слабо связанными, и вы можете добиться этого с помощью абстракции . Основным мотивом этого принципа является разделение зависимостей, поэтому, если класс A изменяется, классу B не нужно заботиться или знать об изменениях.
    Можно рассмотреть на живом примере батарейку пульта от телевизора. Вашему пульту требуется батарея, но это не зависит от марки батареи. Вы можете использовать любую марку XYZ, которую хотите, и она будет работать. Так что можно сказать, что пульт от телевизора слабо связан с названием бренда. Инверсия зависимостей делает ваш код более пригодным для повторного использования.

    Сплошные узоры

    Сплошные узоры

    Твердые фигуры — это трехмерные фигуры, имеющие длину, ширину и высоту.

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

    На твердых фигурах есть лица.

    Грань — это плоская поверхность объемных фигур.


     |

    Лица могут быть разной формы.
    Твердые фигуры могут быть сделаны более чем из 1 формы.



    прямоугольная призма

     |

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

     |

    Прямоугольник
    Прямоугольники: 0
    1
    2
    3
    4
    5
    6

    Квадраты: 0
    1
    2
    3
    4
    5
    6
    Цилиндр
    Круги: 0
    1
    2
    3
    4
    5
    6

    Прямоугольники: 0
    1
    2
    3
    4
    5
    6
    Куб
    Квадраты: 0
    1
    2
    3
    4
    5
    6
    Конус
    Круги: 0
    1
    2
    3
    4
    5
    6
    Квадратная пирамида
    Квадраты: 0
    1
    2
    3
    4
    5
    6

    Треугольники: 0
    1
    2
    3
    4
    5
    6

     |

    Имеет ли сфера плоскость?
    поверхности или лица?

     |

    Соотнесите каждую фигуру с
    правильные части.

    This entry was posted in Популярное