У меня тоже есть сомнения относительно понимания инверсии контроля.(Кажется, что это применение хороших принципов проектирования ОО с придуманным названием) Итак, позвольте мне предположить, что вы новичок, проанализировать ваш пример и прояснить мои мысли о пути.
Мы должны начать с определения интерфейса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();
Это не идеальное решение по следующим причинам:
PriorityQueue
теперь (очень излишне) зависит от ComparisonCreator
.Если он находится на другой сборке, он должен ссылаться на него.Если кто-то изменит ComparisonCreator
, он должен убедиться, что PriorityQueue
не затронут.
Клиентам будет трудно использовать PriorityQueue
.Сначала они должны убедиться, что ComparisonCreator
создан и инициализирован.
Клиентам будет трудно изменить поведение по умолчанию.Предположим, что где-то клиенту нужна другая функция CompareTo
.Нет простого решения.Например, если он изменяет поведение ComparisonCreator<T>
, это может повлиять на других клиентов.Что делать, если есть другие темы.Даже в среде с одним потоком клиенту, вероятно, потребуется отменить изменения в конструкции.Это слишком много усилий, чтобы заставить его работать.
По тем же причинам трудно провести модульное тестирование PriorityQueue
.Нужно настроить всю среду.
Конечно, - и, конечно, вы знали об этом все время - есть гораздо более простой способ решения этой конкретной проблемы.Просто предоставьте в конструкторе функцию CompareTo
:
public PriorityQueue(Func<T,T,int> CompareTo)
{
this.CompareTo = CompareTo;
this.list = new LinkedList<T>();
}
Давайте проверим:
PriorityQueue
не зависит от ComparisonCreator
.
Для клиентов, вероятно, гораздо проще использовать PriorityQueue
.Возможно, им потребуется предоставить функцию CompareTo
, но в худшем случае они всегда могут задать ServiceLocator
, поэтому, по крайней мере, это никогда не будет труднее.
Изменение поведения по умолчаниюочень легко.Просто дайте другую функцию CompareTo
.То, что делает один клиент, не влияет на других клиентов.
Очень легко провести модульное тестирование PriorityQueue
.Нет сложной среды для настройки.Мы можем легко протестировать его с различными CompareTo
функциями и т. Д.
То, что мы сделали, называется «инжекцией конструктора», потому что мы внедрили зависимость в конструктор.Задавая необходимую зависимость при построении, мы смогли преобразовать PriorityQueue
в «самодостаточный» класс.Мы все еще создаем LinkedList<T>
, конкретный класс в конструкции по тем же причинам в примере Stack
: это не реальная зависимость.