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

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

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

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

дебаты зашли в тупик со следующим заявлением:

МОК не ООП, потому что он нарушает инкапсуляцию

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

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

Пример 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() вызов метода, поэтому программист не знает о поведении этого метода, поэтому я не думаю, что это нарушает инкапсуляцию.

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

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

вопросы:

  • является ли МОК прямой реализацией принципа инверсии зависимостей?
  • всегда ли МОК нарушает инкапсуляцию и, следовательно, ООП?
  • следует ли использовать МОК экономно, религиозно или надлежащим образом?
  • в чем разница между МОК и контейнером МОК?

7 ответов


всегда ли МОК нарушает инкапсуляцию и, следовательно, ООП?

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

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

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

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

public class Foo
{
    private readonly int bar;

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

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

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

что произойдет, если мы изменим 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, C# и т. д.) что существует фактическая разница между примитивами (строками, целыми числами, булами и т. д.) и "реальные" объекты. С точки зрения удов, они все одинаковы. Строки также имеют поведение: вы можете превратить их во все-верхний регистр, обратить их и т. д.

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

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

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

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

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

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

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

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

нет, это не так: это просто разная степень полиморфизма. Таким образом, мы могли бы переименовать Bar to 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 также известен как принципы УД. Инкапсуляция (т. е. проектирование по контракту) определяет основные правила. Твердые описываются правила следующие правила.


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

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

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

инверсия управления позволяет нам подключить наш пользовательский код в на производство многократно используемой библиотеки. Другими словами, управление инверсией - это фреймворки. Многоразовая библиотека, которая не применяет инверсию элемента управления, является просто библиотекой. Фреймворк-это многоразовая библиотека, которая тут применить инверсию управления.

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

всегда ли МОК нарушает инкапсуляцию и, следовательно, ООП?

Так как МОК просто подключается к конвейеру структуры,здесь нет ничего, что протекает. Таким образом, реальный вопрос: делает ли инъекция зависимостей разрыв инкапсуляции.

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

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

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

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

в чем разница между МОК и контейнером МОК?

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

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

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

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

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


Подводя итог вопросу:

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

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

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

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

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

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

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

аргумент есть... да, но я просто хочу нанять адвоката, Мне плевать на его книги и услуги. использует. Я получу некоторые случайные каракули от 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 }
    }
}

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

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

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

  • всегда ли МОК разорвать инкапсуляцию, и, следовательно, ООП?

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

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

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

  • Что такое разница между МОК и контейнером МОК?

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


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

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

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


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

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

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

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


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