Подтвердить что ты не робот

Принцип инверсии зависимостей (SOLID) против инкапсуляции (Столпы ООП)

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

Мое понимание этих вещей:

  • Принцип инверсии зависимостей подразумевает, что объекты должны зависеть от абстракций, а не от конкреций - это основной принцип, на котором реализована инверсия шаблона управления и инъекции зависимостей.
  • Инверсия управления - это реализация шаблона принципа инверсии зависимостей, где абстрактные зависимости заменяют конкретные зависимости, позволяя конкретизировать зависимость, которая должна быть указана вне объекта.
  • Injection Dependency - это шаблон проектирования, который реализует Inversion of Control и обеспечивает разрешение зависимостей. Инъекция происходит, когда зависимость передается зависимому компоненту. По сути, шаблон Injection Dependency предоставляет механизм для объединения абстракций зависимостей с конкретными реализациями.
  • Инкапсуляция - это процесс, при котором данные и функциональные возможности, требуемые объектом более высокого уровня, изолированы и недоступны, поэтому программист не знает, как реализован объект.

Дебаты получили точку со следующим утверждением:

IoC не является ООП, потому что он прерывает инкапсуляцию

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

Если есть (потенциально) более одного способа скинуть кошку, то неведите себя так, как будто есть только один.

Пример 1:

class Program {
    void Main() {
        SkinCatWithKnife skinner = new SkinCatWithKnife ();
        skinner.SkinTheCat();
    }
}

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

Пример 2:

class Program {
    // Encapsulation
    ICatSkinner skinner;

    public Program(ICatSkinner skinner) {
        // Inversion of control
        this.skinner = skinner;
    }

    void Main() {
        this.skinner.SkinTheCat();
    }
}

... new Program(new SkinCatWithTeeth());
    // Dependency Injection

Здесь мы наблюдаем принцип инверсии зависимостей и инверсию управления, так как абстрактный (ICatSkinner) предоставляется для того, чтобы позволить конкретным зависимостям быть переданными программистом. Наконец, есть несколько способов обмануть кошку!

Ссора здесь; это разрушает инкапсуляцию? технически можно утверждать, что .SkinTheCat(); по-прежнему инкапсулируется в вызов метода Main(), поэтому программист не знает о поведении этого метода, поэтому я не думаю, что это нарушает инкапсуляцию.

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

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

Вопросы:

  • Является ли IoC прямой реализацией принципа инверсии зависимостей?
  • Всегда ли IoC прерывает инкапсуляцию и, следовательно, ООП?
  • Следует ли использовать IoC экономно, религиозно или надлежащим образом?
  • В чем разница между IoC и контейнером IoC?
4b9b3361

Ответ 1

Всегда ли IoC прерывает инкапсуляцию, а значит, OOP?

Нет, это проблемы, связанные с иерархией. Инкапсуляция является одной из самых непонятых концепций в ООП, но я думаю, что связь лучше всего описать с помощью абстрактных типов данных (ADT). По сути, ADT - это общее описание данных и связанного с ними поведения. Это описание абстрактно; он опускает детали реализации. Вместо этого он описывает ADT в терминах pre - и пост-условиях.

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

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

Можно, например, передать данные в объект через его конструктор:

public class Foo
{
    private readonly int bar;

    public Foo(int bar)
    {
        this.bar = bar;
    }

    // Other members may use this.bar in various ways.
}

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

Что произойдет, если мы изменим bar на целое число на другой конкретный класс?

public class Foo
{
    private readonly Bar bar;

    public Foo(Bar bar)
    {
        this.bar = bar;
    }

    // Other members may use this.bar in various ways.
}

Единственное отличие по сравнению с предыдущим заключается в том, что bar теперь является объектом, а не примитивным. Однако это ложное различие, поскольку в объектно-ориентированном дизайне целое число также является объектом. Это связано только с оптимизацией производительности на разных языках программирования (Java, С# и т.д.), Что существует реальная разница между примитивами (строками, целыми числами, bools и т.д.) И "реальными" объектами. С точки зрения OOD, они все похожи. Строки также имеют поведение: вы можете превратить их во все верхние регионы, перевернуть их и т.д.

Нарушена ли инкапсуляция, если bar является запечатанным/окончательным, конкретным классом с только не виртуальными членами?

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

Что произойдет, если мы разрешим bar иметь единственный виртуальный член?

Является ли инкапсуляция нарушенной этим?

Можем ли мы по-прежнему выражать предварительные и пост-условия о Foo, учитывая, что bar имеет один виртуальный член?

Если bar придерживается Принципа замены Лискова (LSP), это не повлияет. LSP явно заявляет, что изменение поведения не должно изменять правильность системы. Пока этот контракт выполняется, инкапсуляция все еще не повреждена.

Таким образом, LSP (один из принципов SOLID, из которых Принцип инверсии зависимостей является другим ) не нарушает инкапсуляцию; он описывает принцип поддержания инкапсуляции в присутствии полиморфизма.

Изменяется ли заключение, если bar является абстрактным базовым классом? Интерфейс?

Нет, это не так: это только разные степени полиморфизма. Таким образом, мы могли бы переименовать bar в IBar (чтобы предположить, что это интерфейс) и передать его в Foo в качестве своих данных:

public class Foo
{
    private readonly IBar bar;

    public Foo(IBar bar)
    {
        this.bar = bar;
    }

    // Other members may use this.bar in various ways.
}

bar - это еще один полиморфный объект, и до тех пор, пока выполняется LSP, выполняется инкапсуляция.

TL; DR

Там причина SOLID также известна как Принципы OOD. Инкапсуляция (т.е. Конструирование по контракту) определяет основные правила. SOLID описывает рекомендации для соблюдения этих правил.

Ответ 2

Является ли IoC прямой реализацией принципа инверсии зависимостей?

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

конструкция, в которой пользовательские части компьютерной программы получить поток управления из общей библиотеки многократного использования (источник)

Инверсия управления позволяет нам подключить наш пользовательский код к конвейеру библиотеки многократного использования. Другими словами, управление инверсией - это рамки. Библиотека многократного использования, которая не применяет Inversion of Control, представляет собой просто библиотеку. Структура - это библиотека многократного использования, которая применяет инверсию управления.

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

Всегда ли IoC прерывает инкапсуляцию, а значит, OOP?

Поскольку IoC просто подключается к конвейеру фреймворка, здесь нет ничего утечки. Таким образом, реальный вопрос заключается в следующем: инкапсуляция прерывания Injection Dependency Injection.

Ответ на этот вопрос: нет, это не так. Он не разрушает инкапсуляцию из-за двух причин:

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

Следует ли использовать IoC экономно, религиозно или надлежащим образом?

Опять же, вопрос: "Должны ли DIP и DI использоваться экономно". На мой взгляд, ответ: НЕТ, вы действительно должны использовать его во всем приложении. Очевидно, вы никогда не должны применять вещи религиозно. Вы должны применять SOLID принципы, и DIP является важной частью этих принципов. Они сделают ваше приложение более гибким и более удобным для обслуживания, и в большинстве сценариев очень удобно применять принципы SOLID.

В чем разница между IoC и контейнером IoC?

Инъекция зависимостей - это шаблон, который может применяться как с контейнером IoC, так и без него. Контейнер IoC - это просто инструмент, который может помочь построить граф объектов лучше, если у вас есть приложение, которое правильно применяет принципы SOLID. Если у вас есть приложение, которое не применяет принципы SOLID, вам будет трудно использовать контейнер IoC. Вам будет сложно применить инъекцию зависимостей. Или позвольте мне сказать, что вам будет сложно поддерживать ваше приложение. Но в нет способа контейнер IoC является необходимым инструментом. Я разрабатываю и поддерживаю контейнер IoC для .NET, но я не всегда использую контейнер для всех своих приложений. Для больших BLOBA (скучная линия бизнес-приложений) я часто использую контейнер, но для небольших приложений (или служб Windows) я не всегда использую контейнер. Но я почти всегда использую Injection Dependency как шаблон, потому что это самый эффективный способ придерживаться DIP.

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

Но, несмотря ни на что, сказанное выше, обратите внимание, что:

в реальном мире разработчика программного обеспечения, теория кодов полезности [от Роберта К. Мартина Agile Principle, Patterns and Practices]

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

Ответ 3

Подводя вопрос:

У нас есть возможность для Службы создавать свои собственные зависимости.

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

И вопрос не в том, "Почему мы это делаем?" (Потому что мы знаем, что есть огромный список причин). Но вопрос в том, что: "Разве не вариант 2 разрушает инкапсуляцию?"

Мой "прагматичный" ответ

Я думаю, что Марк лучше всего подходит для любых таких ответов, и, как он говорит: "Нет, инкапсуляция - это не то, что люди считают."

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

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

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

sub main() {
    LegalService legalService = new LegalService();

    legalService.SueMyManagerForBeingMean();
}

public class LegalService {
    public void SueMyManagerForBeingMean(){
        // Implementation Details.
    }
}

Но, как оказалось, для выполнения этой работы требуются другие услуги, такие как понимание законодательства на рабочем месте. А также, как выясняется... Я ОЧЕНЬ интересуюсь контрактами, которые подписывает адвокат под моим именем, и другим материалом, который он делает, чтобы украсть мои деньги. Например... Почему, черт возьми, этот интернет-адвокат, базирующийся в Южной Корее? Как это поможет мне!?!? Это не деталь реализации, эта часть цепочек зависимостей, с которыми я с удовольствием справляюсь.

sub main() {
    IWorkLawService understandWorkplaceLaw = new CaliforniaWorkplaceLawService();
    //IWorkLawService understandWorkplaceLaw = new NewYorkWorkplaceLawService();
    LegalService legalService = new LegalService(understandWorkplaceLaw);

    legalService.SueMyManagerForBeingMean();
}

public interface ILegalContract {
    void SueMyManagerForBeingMean();
}

public class LegalService : ILegalContract {
    private readonly IWorkLawService _workLawService;

    public LegalService(IWorkLawService workLawService) {
        this._workLawService = workLawService;
    }

    public void SueMyManagerForBeingMean() {
        //Implementation Detail
        _workLawService.DoSomething; // { implementation detail in there too }
    }
}

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

Ответ 4

Я постараюсь ответить на ваши вопросы. По моему мнению:

  • Является ли IoC прямой реализацией принципа инверсии зависимостей?

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

  • Является ли IoC всегда нарушением инкапсуляции и, следовательно, ООП?

    Я не думаю, что механизм IoC будет нарушать инкапсуляцию. Но может заставить систему стать тесно связанной.

  • Следует ли использовать IoC экономно, религиозно или надлежащим образом?

    IoC может использоваться как во многих шаблонах, таких как Bridge Pattern, где разделение Concretion from Abstraction улучшает код. Таким образом, можно использовать для достижения DIP.

  • В чем разница между IoC и контейнером IoC?

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

Ответ 5

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

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

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

Ответ 6

Я только отвечу на один вопрос, так как многие другие ответили на все остальное. И имейте в виду, что нет правильного или неправильного ответа, просто пользовательские настройки.

Следует ли использовать IoC экономно, религиозно или надлежащим образом? Мой опыт побуждает меня полагать, что инъекция зависимостей должна использоваться только для классов, которые являются общими и могут потребоваться в будущем. Использование его религиозно приведет к тому, что некоторые классы нуждаются в 15 интерфейсах в конструкторе, которые могут занять много времени. Это, как правило, приводит к 20% развитию и 80% дому.

Кто-то поднял пример автомобиля и как строитель автомобиля захочет изменить шины. Впрыск зависимости позволяет менять шины, не заботясь о конкретных деталях реализации. Но если мы принимаем инъекцию зависимости религиозно... Тогда нам нужно также начать создавать интерфейсы с составляющими шин... Ну, а как насчет нитей шины? Как насчет прошивки в этих шинах? Как насчет химического вещества в этих потоках? А как насчет атомов в этих химических веществах? И т.д.... Ладно! Привет! В какой-то момент вам придется сказать "достаточно достаточно"! Позвольте не превращать каждую мелочь в интерфейс... Потому что это может занять слишком много времени. Это нормально, чтобы некоторые классы были самодостаточны и инстанцировались в самом классе! Это быстрее развивается, и создание класса проще, чем тонна.

Только мои 2 цента.

Ответ 7

Я нашел случай, когда инкапсуляция ioc и зависимой инъекции прерывается. Допустим, у нас есть класс ListUtil. В этом классе есть метод, называемый remove duplicates. Этот метод принимает список. Существует интерфейс ISortAlgorith с методом сортировки. Существует класс QuickSort, который реализует этот интерфейс. Когда мы пишем alogorithm для удаления дубликатов, мы должны отсортировать список внутри. Теперь, если RemoveDuplicates разрешает интерфейс ISortAlgorithm в качестве параметра (IOC/Dependency Injection), чтобы позволить расширяемости для других выбирать другой алгоритм для удаления дубликата, мы раскрываем сложность удаления повторяющейся функции класса ListUtil. Таким образом, нарушается камень основания Oops.