Состав и обращение контроля - PullRequest
2 голосов
/ 02 апреля 2012

Я только что натолкнулся на подход Inversion of Control (реализованный с использованием Dependency Injection) для проектирования слабосвязанной архитектуры программного обеспечения. Согласно моему пониманию, подход IOC направлен на решение проблемы, связанной с тесной связью между классами, путем создания объекта класса внутри другого класса, что в идеале не должно происходить (согласно шаблону). Правильно ли мое понимание здесь?

Если выше верно, то как насчет композиции или имеет-отношения (очень важный аспект OO). В качестве примера я пишу свой класс стека, используя уже определенный класс связанного списка, поэтому я создаю экземпляр класса связанного списка внутри своего класса стека. Но в соответствии с МОК это приведет к жесткой связи и, следовательно, к плохому дизайну. Это правда? Я немного запутался здесь между композицией или отношениями и МОК.

Ответы [ 5 ]

4 голосов
/ 02 апреля 2012

Насколько я понимаю, подход МОК направлен на решение проблем, связанных с к тесной связи между классами путем создания экземпляра объекта класс внутри другого класса, который в идеале не должен происходить (согласно шаблон). Правильно ли мое понимание здесь?

Закрой, но ты немного не в себе. Проблема тесной связи решается, когда вы определяете контракты между классами (интерфейсами в Java). Поскольку вам нужны реализации ваших контрактов (интерфейсов), в какой-то момент эти реализации должны быть предоставлены. IoC - это один способ предоставления реализации, но не единственный способ. Таким образом, тесная связь действительно ортогональна к инверсии управления (то есть она не имеет прямого отношения).

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

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

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

В этом случае вы можете рассматривать свой стек как черный ящик, а не управлять им с помощью IoC. Помните, что сам стек является слабой парой, потому что поведение стека можно абстрагировать. Также рассмотрим следующие два определения

class StackImpl implements Stack {
   private List backingList

против

class StackImpl implements Stack {
   private LinkedList backingList

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

Это так далеко, как я бы это взял. Кроме того, если вы используете композицию, вы, безусловно, можете сконфигурировать большинство IoC-контейнеров (если не все) так, чтобы они передавались в конструктор или вызывали сеттеры, так что вы все равно можете иметь отношение has-A .

3 голосов
/ 02 апреля 2012

Хорошие реализации IoC могут выполнять шаблон «имеет», но просто абстрагировать реализацию дочернего элемента.

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

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

1 голос
/ 03 апреля 2012

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

IoC на самом деле довольно широкое понятие , поэтому давайте ограничим поле подходом внедрения зависимостей, на который вы ссылаетесь.Да, Dependency Injection делает то, что вы сказали.

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

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

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

Но в соответствии с МОК это приведет к сильной связи и, следовательно, к плохому дизайну.Это правда?

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

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

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

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

1 голос
/ 03 апреля 2012

У меня тоже есть сомнения относительно понимания инверсии контроля.(Кажется, что это применение хороших принципов проектирования ОО с придуманным названием) Итак, позвольте мне предположить, что вы новичок, проанализировать ваш пример и прояснить мои мысли о пути.

Мы должны начать с определения интерфейсаIStack.

interface IStack<T>
{
    bool IsEmpty();
    T Pop();
    void Push(T item);
}

В некотором смысле мы уже закончили;остальной части кода, вероятно, не будет заботиться о том, реализовали ли мы его со связанными списками, массивами или чем-то еще.StackWithLinkedList : IStack и StackWithArray : IStack будут вести себя одинаково.

class StackWithLinkedList<T> : IStack<T>
{
   private LinkedList<T> list;

   public StackWithLinkedList<T>()
   {
      list = new LinkedList<T>();
   }
}

Итак, StackWithLinkedList полностью владеет list;для его создания не требуется никакой помощи извне, не требуется никакой гибкости (эта линия никогда не изменится), и клиенты StackWithLinkedList не заботятся об этом (у них нет доступа к списку).Короче говоря, это не хороший пример для обсуждения Inversion of Control: нам это не нужно.

Давайте обсудим аналогичный пример, PriorityQueue<T>:

interface IPriorityQueue<T>
{
    bool IsEmpty();
    T Dequeue();
    void Enqueue(T item);
}

Теперь у нас естьпроблема: нам нужно сравнить элементы типа T, чтобы обеспечить реализацию IPriorityQueue.Клиентам по-прежнему все равно, используем ли мы массив, кучу или что-то еще внутри, но им важно, как мы сравниваем элементы.Мы могли бы потребовать T для реализации IComparable<T>, но это было бы ненужным ограничением.Нам нужен какой-то функционал, который будет сравнивать T элементов по нашему запросу:

class PriorityQueue<T> : IPriorityQueue<T>
{
   private Func<T,T,int> CompareTo;
   private LinkedList<T> list;
   //bla bla.
}

Так, что:

  • , если CompareTo(left,right) < 0, то оставлено <вправо (в некотором смысле) </p>

  • , если CompareTo(left,right) > 0, то влево> вправо (в некотором смысле)

  • , если CompareTo(left,right) = 0, то влево =верно (в некотором смысле)

  • (Нам также потребуется CompareTo, чтобы быть последовательными и т. д., но это другая тема)

проблема в том, как инициализировать CompareTo.

Один из вариантов может быть, предположим, что где-то есть универсальный создатель сравнения, используйте создателя сравнения.(Я согласен, пример становится немного глупым)

public PriorityQueue()
{
    this.CompareTo = ComparisonCreator<T>.CreateComparison();
    this.list = new LinkedList<T>();
}

Или, возможно, даже что-то вроде: ServiceLocator.Instance.ComparisonCreator<T>.CreateComparison();

Это не идеальное решение по следующим причинам:

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

  2. Клиентам будет трудно использовать PriorityQueue.Сначала они должны убедиться, что ComparisonCreator создан и инициализирован.

  3. Клиентам будет трудно изменить поведение по умолчанию.Предположим, что где-то клиенту нужна другая функция CompareTo.Нет простого решения.Например, если он изменяет поведение ComparisonCreator<T>, это может повлиять на других клиентов.Что делать, если есть другие темы.Даже в среде с одним потоком клиенту, вероятно, потребуется отменить изменения в конструкции.Это слишком много усилий, чтобы заставить его работать.

  4. По тем же причинам трудно провести модульное тестирование PriorityQueue.Нужно настроить всю среду.

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

public PriorityQueue(Func<T,T,int> CompareTo)
{
    this.CompareTo = CompareTo;
    this.list = new LinkedList<T>();
}

Давайте проверим:

  1. PriorityQueue не зависит от ComparisonCreator.

  2. Для клиентов, вероятно, гораздо проще использовать PriorityQueue.Возможно, им потребуется предоставить функцию CompareTo, но в худшем случае они всегда могут задать ServiceLocator, поэтому, по крайней мере, это никогда не будет труднее.

  3. Изменение поведения по умолчаниюочень легко.Просто дайте другую функцию CompareTo.То, что делает один клиент, не влияет на других клиентов.

  4. Очень легко провести модульное тестирование PriorityQueue.Нет сложной среды для настройки.Мы можем легко протестировать его с различными CompareTo функциями и т. Д.

То, что мы сделали, называется «инжекцией конструктора», потому что мы внедрили зависимость в конструктор.Задавая необходимую зависимость при построении, мы смогли преобразовать PriorityQueue в «самодостаточный» класс.Мы все еще создаем LinkedList<T>, конкретный класс в конструкции по тем же причинам в примере Stack: это не реальная зависимость.

1 голос
/ 02 апреля 2012

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

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