Dependency inversion principle реализуется с помощью. Инверсия зависимостей

Последнее обновление: 11.03.2016

Принцип инверсии зависимостей (Dependency Inversion Principle) служит для создания слабосвязанных сущностей, которые легко тестировать, модифицировать и обновлять. Этот принцип можно сформулировать следующим образом:

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

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

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

Class Book { public string Text { get; set; } public ConsolePrinter Printer { get; set; } public void Print() { Printer.Print(Text); } } class ConsolePrinter { public void Print(string text) { Console.WriteLine(text); } }

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

Теперь попробуем привести наши классы в соответствие с принципом инверсии зависимостей, отделив абстракции от низкоуровневой реализации:

Interface IPrinter { void Print(string text); } class Book { public string Text { get; set; } public IPrinter Printer { get; set; } public Book(IPrinter printer) { this.Printer = printer; } public void Print() { Printer.Print(Text); } } class ConsolePrinter: IPrinter { public void Print(string text) { Console.WriteLine("Печать на консоли"); } } class HtmlPrinter: IPrinter { public void Print(string text) { Console.WriteLine("Печать в html"); } }

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

Book book = new Book(new ConsolePrinter()); book.Print(); book.Printer = new HtmlPrinter(); book.Print();

DISCLAIMER : У автора этой статьи нет цели подорвать авторитет или каким-то образом обидеть столь уважаемого камрада, как «дядюшка» Боб Мартин. Речь здесь идет скорее о более тщательном обдумывании принципа инверсии зависимостей и анализ примеров, использованных при его описании.

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

Принцип инверсии зависимостей звучит так :

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

Разбиение на слои

У лука есть слои, у торта есть слои, у людоедов есть слои и у программных систем – тоже есть слои! – Шрек (с)
Любая сложная система является иерархичной: каждый слой строится на базе проверенного и хорошо работающего слоя более низкого уровня. Это позволяет сосредоточиться в каждый момент времени на ограниченном наборе концепций, не задумываясь о том, как реализованы слои нижнего уровня.
В результате мы получаем примерно следующую диаграмму:

Рисунок 1 – «Наивная» схема разбиения на слои

С точки зрения Боба Мартина такая схема разбиения системы на слои является наивной . Недостатком такого дизайна является «коварная особенность: слой Policy зависит от изменений во всех слоях на пути к Utility . Эта зависимость транзитивна .» .

Хм… Весьма необычное утверждение. Если говорить о платформе.NET, то зависимость будет транзитивной только в том случае, если текущий модуль будет «выставлять» модули нижних уровней в своем открытом интерфейсе . Другими словами, если в Mechanism Layer есть открытый класс, принимающий в качестве аргумента экземпляр StringUtil (из Utility Layer ), то все клиенты уровня Mechanism Layer становятся зависимыми на Utility Layer . В противном случае, транзитивность изменений отсутствует: все изменения нижнего уровня ограничены текущем уровнем и не распространяются выше .

Чтобы понять мысль Боба Мартина нужно вспомнить, что впервые принцип инверсии зависимостей был описан в далеком 1996-м году , и в качестве примеров использовался язык С++. В исходной статье сам автор пишет о том, что проблема транзитивности есть лишь в языках без четкого разделения интерфейса класса от реализации . В С++ и правда проблема транзитивных зависимостей актуальна: если файл PolicyLayer . h включает посредством директивы «include» MechanismLayer . h , который, в свою очередь включает UtilityLayer . h , то при любом изменении в заголовочном файле UtilityLayer . h (даже в «закрытой» секции классов, объявленных в этом файле) нам придется перекомпилировать и развернуть заново всех клиентов. Однако в С++ эта проблема решается путем использования идиомы PIml , предложенной Гербом Саттером и сейчас тоже не столь актуальна.

Решение этой проблемы с точки зрения Боба Мартина заключается в следующем:

«Слой более высокого уровня объявляет абстрактный интерфейс служб, в которых он нуждается. Затем слои нижних уровней реализуются так, чтобы удовлетворять этим интерфейсам. Любой класс, расположенный на верхнем уровне, обращается к слою соседнего снизу уровня через абстрактный интерфейс. Таким образом, верхние слои не зависят от нижних. Наоборот, нижние слои зависят от абстрактного интерфейса служб, объявленного на более высоком уровне… Таким образом, обратив зависимости, мы создали структуру, одновременно более гибкую, прочную и подвижную



Рисунок 2 – Инвертированные слои

В некотором роде такое разбиение разумно. Так, например, при использовании паттерна наблюдатель , именно наблюдаемый объект (observable) определяет интерфейс взаимодействия с внешним миром, поэтому никакие внешние изменения не могут на него повлиять.

Но с другой стороны, когда речь заходит именно о слоях, которые представляются обычно сборками (или пакетами в терминах UML), то предложенный подход вряд ли можно назвать жизнеспособным. По своему определению, вспомогательные классы нижнего уровня используются в десятке разных модулях более высокого уровня. Utility Layer будет использоваться не только в Mechanism Layer , но еще и в Data Access Layer , Transport Layer , Some Other Layer . Должен ли он в таком случае реализовывать интерфейсы, определенные во всех модулях более высокого уровня?

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

Понятие абстракции

Многие термины настолько «въедаются» в наш мозг , что мы перестаем обращать на них внимание. Для большинства «объектно-ориентированных» программистов это означает, что мы перестаем задумываться над многими заезженными терминами, как «абстракция», «полиморфизм», «инкапсуляция». Чего над ними думать, ведь все и так понятно? ;)

Однако для того, чтобы точно понять смысл принципа инверсии зависимостей и второй части определения, нам нужно вернуться к одному из этих фундаментальных понятий. Давайте посмотрим на определение термина «абстракция» из книги Гради Буча :

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

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

Давайте вернемся к определению: Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Какой пример возникает в голове теперь, после того, как мы вспомнили, что же такое абстракция ? Когда абстракция начинает зависеть от деталей? Примером нарушения этого принципа может служить абстрактный класс GZipStream , который принимает MemoryStream , а не абстрактный класс Stream :

Abstract class GZipStream { // Абстракция GZipStream принимает конкретный поток protected GZipStream(MemoryStream memoryStream) {} }

Другим примером нарушения этого принципа может быть абстрактный класс репозитория из слоя доступа к данным, принимающий в конструкторе PostgreSqlConnection или строку подключения для SQL Server, что делает любую реализацию такой абстракции завязанной на конкретную реализацию. Но это ли имеет ввиду Боб Мартин? Если судить по примерам, приведенных в статье или в книге, то под понятием «абстракции» Боб Мартин понимает нечто совсем иное.

Принцип DIP по Мартину

Для объяснения своего определения Боб Мартин дает следующее пояснение.

Чуть упрощенная, но все еще весьма действенная интерпретация принципа DIP выражается простым эвристическим правилом: «Зависеть надо от абстракций». Оно гласит, что не должно быть зависимостей от конкретных классов; все связи в программе должны вести на абстрактный класс или интерфейс.

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

В качестве же иллюстрации нарушения принципа DIP вообще, и первого «проясняющего» пункта, в частности, приводится следующий пример:

Public class Button { private Lamp lamp; public void Poll() { if (/* какое-то условие */) lamp.TurnOn(); } }

Теперь давайте еще раз вспомним о том, что такое абстракция и ответим на вопрос: есть ли здесь «абстракция», которая зависит от деталей? Пока вы думаете об этом или ищите глазами абзац, в котором находится ответ на этот вопрос, я хочу сделать небольшое отступление.

У кода есть одна интересная особенность. За редким исключением, код сам по себе не может быть корректным или не корректным; баг это или фича зависит от того, что от него ожидается. Даже если нет формальной спецификации (что является нормой), код некорректен лишь в том случае, когда он делает не то, что от него требуется или предполагается. Именно этот принцип лежит в основе контрактного программирования , в котором спецификация (намерения) выражаются непосредственно в коде в форме предусловий, постусловий и инвариантов.

Глядя на класс Button я не могу сказать ошибочен дизайн или нет. Я могу точно сказать, что имя класса не соответствует его реализации. Класс нужно переименовать в LampButton или убрать из класса Button поле Lamp .

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

Во-первых, я не вижу в данном примере «стратегий верхнего уровня» и «модулей нижнего уровня» : с моей точки зрения, классы Button и Lamp находятся на одном уровне абстракции (во всяком случае, я не вижу аргументов, доказывающих обратное). Тот факт, что класс Button может кем-то управлять не делает его более высокоуровневым. Во-вторых, здесь нет «абстракции, зависящей от деталей», здесь есть «реализация абстракции, зависящая от деталей», что совсем не одно и тоже.

Решение по Мартину такое:



Рисунок 3 – «Инвертирование зависимостей»

Лучше ли данное решение? Давайте посмотрим…

Главным плюсом инвертирования зависимостей «по Мартину» является инвертирование владения. В исходном дизайне, при изменении класса Lamp пришлось бы изменяться классу Button . Теперь класс Button «владеет» интерфейсом ButtonServer , а он не может измениться из-за изменения «нижних уровней», таких как Lamp . Все как раз наоборот: изменение класса ButtonServer возможно только под воздействием изменений класса Button, что приведет к изменению всех наследников класса ButonServer !

На самом деле все принципы SOLID между собой сильно связаны и основная их цель — помощь в создании качественного, способного к масштабированию, программного обеспечения. Но последний принцип SOLID на их фоне действительно выделяется. Для начала посмотрим на формулировку данного принципа. Итак, принцип инверсии зависимостей (Dependency Inversion Principle — DIP): «Зависимость на абстракциях. Нет зависимости на что-то конкретное.» . Небезызвестный специалист в области разработки ПО, Роберт Мартин , также особенно выделяет принцип DIP и представляет его просто как результат следованию другим принципам SOLID — принципу открытости/закрытости и принципу подстановки Лисков. Напомним, что первый говорит о том, что класс не должен модифицироваться для внесения новых изменений, а второй касается наследования и предполагает безопасное использование производных типов некоторого базового типа без нарушения правильности работы программы. Роберт Мартин изначально сформулировал этот принцип следующим образом:

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

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

То есть разрабатывать классы нужно, оперируя абстракциями, а не конкретными их реализациями. И если следовать принципам OCP и LSP , то именно этого мы и добьемся. Поэтому вернемся немного назад, к уроку, посвященному . Там в качестве примера мы рассматривали класс Bard , который в самом начале был жестко привязан к классу Guitar , представляющему конкретный музыкальный инструмент:

public class Bard { private Guitar guitar; public Bard(Guitar guitar) { this.guitar = guitar; } public void play() { guitar.play(); } }

public class Bard {

private Guitar guitar ;

public Bard (Guitar guitar )

this . guitar = guitar ;

public void play ()

guitar . play () ;

В случае, если бы мы захотели добавить в данный класс поддержку других музыкальных инструментов, то нам так или иначе пришлось бы модифицировать данный класс. Это явное нарушение принципа OCP . И, возможно, вы уже заметили, что это также нарушения принципа DIP , так как в нашем случае наша абстракция оказалась зависимой от деталей. С точки зрения дальнейшего расширения нашего класс это совсем не хорошо. Чтобы наш класс соответствовал условиям принципа OCP мы добавили в систему интерфейс Instrument , который реализовывали конкретные классы, представляющие те или иные виды музыкальных инструментов.

Файл Instrument.java :

public interface Instrument { void play(); }

public interface Instrument {

void play () ;

Файл Guitar.java :

class Guitar implements Instrument{ @Override public void play() { System.out.println("Play Guitar!"); } }

class Guitar implements Instrument {

@Override

public void play ()

System . out . println ("Play Guitar!" ) ;

Файл Lute.java :

public class Lute implements Instrument{ @Override public void play() { System.out.println("Play Lute!"); } }

public class Lute implements Instrument {

@Override

public void play ()

System . out . println ("Play Lute!" ) ;

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

public class Bard { private Instrument instrument; public Bard() { } public void play() { instrument.play(); } public void setInstrument(Instrument instrument) { this.instrument = instrument; } }

public class Bard {

private Instrument instrument ;

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

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

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

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

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

Скажем, мы решили заказать торт ко дню рождения. Для этого мы отправились в замечательную пекарню на углу улицы. Узнали, могут ли они для нас испечь торт под громким названием “Роскошь”, и получив позитивный ответ, заказали. Всё просто.

А теперь определимся с тем, какие абстракции нужно предусмотреть в этом коде. Для этого просто зададим себе несколько вопросов:

  • Почему именно торт? Можно было заказать и пирог или кексы
  • Почему именно ко дню рождения? А если бы это была свадьба или выпускной?
  • Почему именно “Роскошь”, а вдруг мне больше нравится “Муравейник” или “Пражский”?
  • Почему именно в эту пекарню, а не в кондитерскую мастерскую в центре города или куда-нибудь ещё?

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

Теперь займёмся конструированием самих типов.

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

Interface IPastry { string Name { get; set; } }

А вот и конкретная реализация, под наш случай – торт ко дню рождения. Как видим, традиционно торт ко дню рождения предполагает свечки)))

Class BirthdayCake: IPastry { public int NumberOfCandles { get; set; } public string Name { get; set; } public override string ToString() { return String.Format("{0} with {1} nice candles", Name, NumberOfCandles) ; } }

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

Следующий вопрос – это чем отличаются друг от друга кондитерские изделия (кроме названия, конечно). Конечно же, рецептом!

А в понятие рецепта входит список ингредиентов и описание процесса приготовления. Поэтому для понятия ингредиент у нас будет отдельный интерфейс:

Interface IIngredient { string IngredientName { get;set;} double Quantity { get; set; } string Units { get; set; } }

А вот и сами ингредиенты для нашего торта: мука, масло, сахар и сливки:

Class Flour:IIngredient { public string IngredientName { get; set; } public double Quantity { get; set; } public string Units { get; set; } public string Quality { get; set; } } class Butter: IIngredient { public string IngredientName { get; set; } public double Quantity { get; set; } public string Units { get; set; } } class Sugar: IIngredient { public string IngredientName { get; set; } public double Quantity { get; set; } public string Units { get; set; } public string Kind { get; set; } } class Creme: IIngredient { public string IngredientName { get; set; } public double Quantity { get; set; } public string Units { get; set; } public double Fat { get; set; } }

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

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

Interface IRecipe { Type PastryType { get; set; } string Name { get;set;} IList Ingredients { get;set;} string Description { get;set;} }

А конкретно класс, представляющий рецепт торта ко дню рождения, выглядит так:

Class BirthdayCakeRecipe: IRecipe { public Type PastryType { get; set; } public string Name { get;set;} public IList Ingredients { get; set; } public string Description { get; set; } public BirthdayCakeReipe() { Ingredients = new List(); } }

Теперь перейдём к нашей замечательной пекарне на углу улицы.

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

Interface IBakery { IPastry Bake(IRecipe recipe); }

А вот и класс, представляющий нашу пекарню:

Class NiceBakeryOnTheCornerOFMyStreet { Dictionary menu = new Dictionary(); public void AddToMenu(IRecipe recipe) { if (!menu.ContainsKey(recipe.Name)) { menu.Add(recipe.Name, recipe); } else { Console.WriteLine("It is already on the menu"); } } public IRecipe FindInMenu(string name) { if (menu.ContainsKey(name)) { return menu; } Console.WriteLine("Sorry...currently we don"t have " + name); return null; } public IPastry Bake(IRecipe recipe) { if (recipe != null) { IPastry pastry = Activator.CreateInstance(recipe.PastryType) as IPastry; if (pastry != null) { pastry.Name = recipe.Name; return pastry as IPastry; } } return null; } }

Осталось только протестировать работу кода:

Class Program { static void Main() { //creating an inctance of the bakery class var bakery = new NiceBakeryOnTheCornerOFMyStreet(); //preparing ingredients for the recipe var flour = new Flour() { IngredientName = "Flour", Quantity = 1.5, Units = "kg" }; var butter = new Butter() { IngredientName = "Butter", Quantity = 0.5, Units = "kg" }; var sugar = new Sugar() { IngredientName = "Sugar", Quantity = 0.7, Units = "kg" }; var creme = new Creme() { IngredientName= "Creme", Quantity = 1.0, Units = "liters" }; //and this is the recipe itself var weddingCakeRecipe = new BirthdayCakeRecipe() { PastryType = typeof(BirthdayCake), Name = "Birthday Cake Luxury", Description = "description on how to make a beautiful birthday cake" }; weddingCakeRecipe.Ingredients.Add(flour); weddingCakeRecipe.Ingredients.Add(butter); weddingCakeRecipe.Ingredients.Add(sugar); weddingCakeRecipe.Ingredients.Add(creme); //adding our cake recipe to the bakery"s menu bakery.AddToMenu(weddingCakeRecipe); //now let"s order it!! BirthdayCake cake = bakery.Bake(bakery.FindInMenu("Birthday Cake Luxury")) as BirthdayCake; //adding some candles ;) cake.NumberOfCandles = 10; //and here we are!!! Console.WriteLine(cake); } }

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

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

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

А теперь подумаем над последствиями нарушения принципа инверсии зависимости:

  1. жесткость (в систему было бы очень тяжело внести какие-то изменения, потому что каждое изменение затрагивало много различных ее частей).
  2. хрупкость (при внесении каких-либо изменения в одну часть системы, уязвимыми становятся другие её части, и порой это не слишком очевидно на первый взгляд).
  3. неподвижность (о повторном использовать кода в других системах можно забыть, поскольку модули сильно связаны между собой).

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

2 ответов

Хороший вопрос - слово inversion несколько удивительно (так как после применения DIP , модуль зависимостей нижнего уровня, очевидно, t теперь depend на модуле вызывающего абонента более высокого уровня: либо вызывающий, либо зависимый теперь более слабо связаны через дополнительную абстракцию).

Можно спросить, почему я использую слово "инверсия". Честно говоря, это связано с тем, что более традиционные методы разработки программного обеспечения, такие как структурированный анализ и дизайн, имеют тенденцию создавать программные структуры, в которых модули высокого уровня зависят от модулей низкого уровня и в которых абстракции зависят от деталей. На самом деле одной из целей этих методов является определение иерархии подпрограмм, которая описывает, как модули высокого уровня выполняют вызовы модулям низкого уровня.... Таким образом, структура зависимостей хорошо спроектированной объектно-ориентированной программы "инвертируется" относительно структуры зависимостей, которая обычно является результатом традиционных процедурных методов.

Один момент, который следует отметить при чтении бумаги дяди Боба на DIP, - это то, что С++ не (и в момент написания, но не имеет) имеют интерфейсы, поэтому достижение этой абстракции в С++ обычно реализуется посредством абстрактного/чистого виртуального базового класса, тогда как в Java или С# абстракция для ослабления связи обычно заключается в развязывании путем абстрагирования интерфейса от зависимости и связывания модуля более высокого уровня (s) к интерфейсу.

Edit Просто уточнить:

"В некотором месте я также вижу, что он называется инверсией зависимостей"

Инверсия: Инвертирование управления зависимостями из приложения в контейнер (например, Spring).

Инъекция зависимостей:

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

Как насчет инверсии управления (IoC)?

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

Инверсия управления в качестве ориентира для проектирования служит для следующих целей:

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

Для получения дополнительной информации смотрите.

2024 academy-fundraising.ru. Бизнес академия.