Каноническое определение принципа:
Клиенты не должны попадать в зависимость от методов, которыми они не пользуются
Этот принцип про интерфейсы, про то как их надо создавать.
Интерфейс является абстракцией внешнего представления класса и описывает все доступные методы для работы с ним. В языках, в которых нет поддержки интерфейсов, они эмулируются через классы с пустыми методами. То есть интерфейсы это классы у которых есть исключительно публичные методы, и у них нет реализации, они должен реализовываться классами их использующими.
Цель принципа:
Пример
Допусти у нас есть класс Product и интерфейс IProduct очевидно что не все классы Product будут поддерживать все его возможные интерфейсы
interface IProduct { function getPrice() function getColor() function getModel() function getPublishYear() function getMaxDiscount() function checkPromocode(promocode) }
В нашем пример
Для того что бы избавиться от этой проблемы, мы делим наш класс на состовлящие:
interface IBaseInfo { function getPrice() function getColor() } interface ICharacteristic { function getModel() function getPublishYear() } interface IDiscount { function getMaxDiscount() funciton checkPromocode(promocode) }
Принцип применяется
Опасности
Добавление метода в интерфейс заставляет реализовывать его в классах наследниках
Заранее хорошо продумывайте абстракции. То есть заранее продумывайте интерфейс а потом класс. Если происходит наоборот то есть с начало реализовывается класс а потом часть его методов переходит в интерфейс, то стоит задуматься а все ли я делаю правильно. Это пагубная привычка и признак возможной проблемы.
Суть принципа
Много специализированных интерфейсов лучше, чем один универсальный.
Каноническое определение принципа:
Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба типа модулей обязаны зависеть от абстракций. Абстракции не должны зависеть от подробностей. Подробностям следует зависеть от абстракций
Достаточно большое определение, и возможно тяжелое для понимания когда читаешь его в первый. Поэтому давайте посмотрим на картинки:
На этой картинке отображена иерархия модулей. High Level Class это первичные алгоритмы высокого уровня которые могут вызвать Low level class, то есть алгоритмы более низкого уровня. Так вот на этой картинки отображено что алгоритмы высокого уровня не должны напрямую зависеть от модулей низкого уровня. Они должны быть связаны через абстракции, в данном случае через интерфейсы.
Далее для того что бы понять, что такое инверсия зависимостей давайте разберем, что такое прямая зависимость. На картинке ниже отображения прямая зависимость компонентов.
В этом случае Component A (например компонент проверки права доступа) запускает Component B (например какой нибудь компонент бизнес логики), которые в свою очередь запускает Component C (например какой нибудь вызов ORM). В данном случае мы видим что все компоненты жестко связанны и разделить их не возможно. Поэтому получается что все зависит в прямом направление.
В коде эта конструкция могло бы выглядеть как то так:
class Controller { function getMethod() { businessLogic = new BusinessLogic() } } class BusinessLogic { function getProduct() { orm = new ORM() } } class ORM { }
Что бы решить все эти проблемы нам на помощь приходит инверсия зависимостей. Представим наш первый рисунок в виде инверсии зависимостей:
Тут видно что все наши зависимости стали инвертируемы. То есть теперь не верхние модули зависят от нижних, а нижние от верхних. На этом рисунки у каждого компонента есть свой собственный интерфейс, и этот компонент знает о нижнем классе, только то что может знать интерфейс. При этом модуль нижнего уровня не может общаться с модулем верхнего уровня напрямую, только через его интерфейс.
Рассмотрим пример инверсии зависимостей в коде. Пусть у нас есть следующий пример кода с прямой зависимостью.
class Order { function getTotalAmount() { discount = new Discount() double totalPrice = discount.calculate() } } order = new Order()
Тут видно что мы в методе getTotalAmount напрямую вызываем класс Discount. Теперь перепишем эту конструкцию с использованием принципа DIP. В примере ниже мы используем абстрактные средства языка интерфейсы:
interface IDiscount { function calculate() } class Order { function getTotalAmount(Discount discount) { double totalPrice =discount.calculate() } } сlass VIPDiscount interface IDiscount { function calculate() {...} } order = new Order().getTotalAmount(new VIPDiscount())
В этом пример мы описали интерфейс IDiscount и создали специальный класс VIPDiscount использующий этот интерфейс. А потом при создание класса Order и вызове метода getTotalAmount мы переделали в него наш специальный класс VIPDiscount. Таким образом мы избавились от прямой зависимости класса Order от класса Discount.
Рассмотрим еще один пример но теперь без использования абстрактных средств языка. Далее у нам будет класс Printer который будет использовать для печати данных в разных форматах. Достаточно типичный пример. Внутри основного класса Printer вызываются два, более низший класса PdfFormatter и HtmlFormatter
class PdfFormatter function format(data) # format data to Pdf logic class HtmlFormatter function format(data) # format data to Html logic class Printer function Printer(data) this.data = data function print_pdf() PdfFormatter(this.data) function print_html() HtmlFormatter(this.data)
Теперь перепишем этот пример с использованием принципа DIP:
class PdfFormatter function format(data) # format data to Pdf logic class HtmlFormatter function format(data) # format data to Html logic class Printer function Printer(data) this.data = data function print(PdfFormatter formatter) formatter.format(this.data)
В приведенном выше коде класс Printer — объект высокого уровня — теперь не зависит напрямую от реализации объектов низкого уровня — HtmlFormatter и PdfFormatter. Кроме того, все модули зависят от абстракции. Теперь наша высокоуровневая функциональность отделена от всех низкоуровневых деталей, поэтому мы можем легко изменить низкоуровневую логику без последствий для всей системы.
Данный принцип позволяет создать слоистость приложения
Более высокие модули реализуют бизнес модель приложения.
Низкие модули реализуют конкретные действия (запросы в БД, сложные вычисления и т.д.) Их удобно переиспользовать в других программах в виде библиотек и общих модулей.
Суть принципа
Зависимости должны строиться относительно абстракций, а не деталей.
В заключение, помните, что принципы SOLID сами по себе не гарантируют отличную объектно-ориентированную архитектуру. Применяйте SOLID принципы разумно. Постарайтесь хорошо понять какую именно проблему вы решаете, и действительно ли эта проблема представляет собой риск для вашей системы. Например, чрезмерное разделение классов в соответствии с принципом SRP может привести к низкой согласованностью, и даже к потере производительности.
Простая установка галочек и следованию утверждению «Теперь мой код соответствует принципам разработки SOLID» — неправильный подход. Помните утверждение, что чистый код нельзя написать, просто следуя набору правил. Однако, правильное применении SOLID рекомендаций может помочь вам создать архитектуру системы, которую легко модифицировать и расширять с течением времени, и именно к этому должен стремиться каждый разработчик.
Краткий перевод: https://vuejs.org/guide/components/v-model.html Основное использование v-model используется для реализации двусторонней привязки в компоненте. Начиная с Vue…
Сегодня мы рады объявить о выпуске Vue 3.4 «🏀 Slam Dunk»! Этот выпуск включает в…
Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue…
Недавно, у меня истек сертификат и пришлось заказывать новый и затем устанавливать на хостинг с…
Каким бы ни было ваше мнение о JavaScript, но всем известно, что работа с датами…
Все, кто следит за последними событиями в мире адаптивного дизайна, согласятся, что введение контейнерных запросов…