Чому мені потрібен контейнер IoC на відміну від прямого коду DI? [зачинено]


598

Я деякий час використовував Dependency Injection (DI), вводячи або конструктор, властивість чи метод. Я ніколи не відчував необхідності використовувати контейнер інверсії управління (IoC). Однак чим більше я читаю, тим більше тиску я відчуваю від громади, щоб використовувати контейнер IoC.

Я грав з .NET-контейнерами, такими як StructureMap , NInject , Unity та Funq . Я все ще не бачу, як контейнер IoC буде отримувати користь / покращувати мій код.

Я також боюся починати використовувати контейнер на роботі, оскільки багато моїх колег побачать код, який вони не розуміють. Багато з них можуть неохоче вивчати нові технології.

Будь ласка, переконайте мене, що мені потрібно використовувати контейнер IoC. Я буду використовувати ці аргументи, коли спілкуюся з колегами-розробниками на роботі.


6
Хороше питання. Я відповідаю на це в коментарях, оскільки мені дуже нова ідея МОК. Це схоже на ідею підключення та відтворення компонентів та нещільного з’єднання. Чи буде у вас потреба використовувати якийсь інший компонент замість поточного, це ґрунтується на потребі. Використання МОК полягає в підготовці коду до такої зміни, якщо вона виникає ІМО.
шахкалпеш

3
@shahkalpesh, але я можу досягти вільної зв'язку з простим DI. Однак я бачу вашу думку в тому випадку, якщо я використовую файл конфігурації. Однак я не впевнений, чи мені подобається використовувати конфігураційний файл. Налаштування файлів дуже багатослівне, труднощі з рефакторингом та перемиканням між декількома файлами.
Вадим

110
просто додати коментар, оскільки, здається, ви шукаєте причини в основному використовувати IoC; але про ваше питання потрібно сказати щось інше. Ви не можете написати свій код до рівня гіршої людини у вашій команді чи не побоюєтесь, що вони не хочуть вчитися. Ви несете відповідальність не лише за те, щоб бути професіоналом, але і за своє майбутнє, щоб рости, і ваша команда не повинна стримувати вас.
Кевін Шеффілд

4
@Kevin, насправді ми використовуємо IoC. Навчити всіх про IoC було не так важко, як думали.
Вадим

21
Я поняття не маю, чому модератори закривають важливі та корисні питання. Можливо, у цьому випадку Він не зрозуміє питання, або він не розуміє, для чого люди використовують stackexchange? Якщо це "не дуже підходить для формату запиту Q&A", то, можливо, вважайте, що ваша політика неправильна, і не всі ці сотні корисних питань із сотнями голосів і корисних відповідей.
NickG

Відповіді:


441

Нічого собі, не можу повірити, що Джоел прихилиться до цього:

var svc = new ShippingService(new ProductLocator(), 
   new PricingService(), new InventoryService(), 
   new TrackingRepository(new ConfigProvider()), 
   new Logger(new EmailLogger(new ConfigProvider())));

над цим:

var svc = IoC.Resolve<IShippingService>();

Багато людей не усвідомлюють, що ланцюжок ваших залежностей може бути вкладений в гніздо, і швидко стає непростим їх вручну. Навіть на заводах дублювання вашого коду просто не варто.

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


Гаразд, давайте виправдаємо це ще більше. Скажімо, у вас є деякі об'єкти або модельні об'єкти, які ви хочете прив’язати до розумного інтерфейсу користувача. Цей розумний інтерфейс (ми будемо називати його Shindows Morms) хоче, щоб ви реалізували INotifyPropertyChanged, щоб він міг відстежувати зміни та оновити інтерфейс відповідно.

"Добре, це не звучить так важко", тому ви почнете писати.

Ви починаєте з цього:

public class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime CustomerSince { get; set; }
    public string Status { get; set; }
}

..і закінчимо це :

public class UglyCustomer : INotifyPropertyChanged
{
    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            string oldValue = _firstName;
            _firstName = value;
            if(oldValue != value)
                OnPropertyChanged("FirstName");
        }
    }

    private string _lastName;
    public string LastName
    {
        get { return _lastName; }
        set
        {
            string oldValue = _lastName;
            _lastName = value;
            if(oldValue != value)
                OnPropertyChanged("LastName");
        }
    }

    private DateTime _customerSince;
    public DateTime CustomerSince
    {
        get { return _customerSince; }
        set
        {
            DateTime oldValue = _customerSince;
            _customerSince = value;
            if(oldValue != value)
                OnPropertyChanged("CustomerSince");
        }
    }

    private string _status;
    public string Status
    {
        get { return _status; }
        set
        {
            string oldValue = _status;
            _status = value;
            if(oldValue != value)
                OnPropertyChanged("Status");
        }
    }

    protected virtual void OnPropertyChanged(string property)
    {
        var propertyChanged = PropertyChanged;

        if(propertyChanged != null)
            propertyChanged(this, new PropertyChangedEventArgs(property));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Це огидний сантехнічний код, і я стверджую, що якщо ви пишете такий код вручну, ви крадете у свого клієнта . Є кращий, розумніший спосіб роботи.

Коли-небудь чули цей термін, працюйте розумніше, а не важче?

Ну уявіть, якийсь розумний хлопець із вашої команди підійшов і сказав: "Ось простіший спосіб"

Якщо ви зробите свої властивості віртуальними (заспокойтеся, це не така вже й велика угода), то ми можемо переплутати цю властивість власності автоматично. (Це називається AOP, але не хвилюйтеся щодо назви, зосередьтесь на тому, що він буде робити для вас)

Залежно від того, який інструмент IoC ви використовуєте, ви можете зробити щось таке:

var bindingFriendlyInstance = IoC.Resolve<Customer>(new NotifyPropertyChangedWrapper());

Пуф! Все це керівництво INotifyPropertyChanged BS тепер автоматично генерується для вас на кожному встановленні віртуальної властивості відповідного об'єкта.

Це магія? ТАК ! Якщо ви можете довіритись тому, що цей код виконує свою роботу, то ви можете сміливо пропустити всю цю властивість, обертаючи mumbo-jumbo. Ви маєте вирішити бізнес-проблеми.

Ще кілька цікавих застосувань інструменту IoC для AOP:

  • Деклараційні та вкладені транзакції бази даних
  • Декларативний та вкладений блок роботи
  • Ведення журналів
  • Умови до / після публікації (дизайн за контрактом)

28
На жаль, ви забули зробити всі властивості віртуальними, і це не спрацювало. Чи потрібен час, щоб витратити налагодження цієї крадіжки у вашого клієнта? (Вибачте, якщо ви не використовуєте DynamicProxy.: P)
Том

17
Ви жертвуєте багато ясності, використовуючи обгортку INotifyPropertyChanged. Якщо вам знадобиться більше декількох хвилин, щоб написати цю сантехніку для початку, то ви не в тому бізнесі. Менше - не більше, коли мова йде про багатослівність вашого коду!
перекрив

39
@overured - я абсолютно не згоден. По-перше, звідки в цьому прикладі значення ясності? Найближчий споживач. Споживач прямо вимагає INotifyPropertyChanged. Тільки тому, що ми можемо створити цей тип коду з генерацією мікро- чи макрокодів, це не означає, що це не дуже добре. Цей пістолет INotifyPropertyChanged - це просто гнилий, мирський, сантехнічний і фактично шкодить читабельності відповідного класу.
Бен Шейрман

31
Позначено внизу, оскільки ви плутаєте шаблон введення залежностей та шаблон локатора послуг. Перший призначений використовувати замість останнього. Наприклад, об'єкт (або взагалі вся система) ніколи не повинен викликати Resolve (...), щоб отримати екземпляр IShippingService. Об'єкти, яким потрібна IShippingService, повинні отримувати посилання на екземпляр, який вони повинні використовувати під час їх побудови, а деякий вищий рівень відповідає за з'єднання двох об'єктів разом.
Нат

30
Що б це не було (IoC, DynamicProxy, AOP або чорна магія ) чи може хтось зв’язати мене з конкретною статтею / конкретною документацією в рамках, що надає більше деталей для досягнення цього?
fostandy

138

Я з тобою, Вадиме. Контейнери IoC беруть просту, елегантну та корисну концепцію, і роблять це щось, що вам доведеться вивчити протягом двох днів із посібником на 200 сторінок.

Я особисто здивований тим, як спільнота IoC взяла красиву, елегантну статтю Мартіна Фаулера і перетворила її на купу складних рамок, як правило, з 200-300 посібниками з сторінок.

Я намагаюся не бути судженням (HAHA!), Але я думаю, що люди, які використовують контейнери IoC, (A) дуже розумні і (B) не мають емпатії до людей, які не такі розумні, як вони. Все має для них ідеальний сенс, тому у них виникають проблеми з розумінням того, що багато звичайних програмістів знайдуть поняття заплутаними. Це прокляття знань . Люди, які розуміють контейнери IoC, не можуть повірити, що є люди, які цього не розуміють.

Найціннішою перевагою використання контейнера IoC є те, що ви можете мати перемикач конфігурації в одному місці, що дозволяє змінювати між, скажімо, тестовим режимом та режимом виробництва. Наприклад, припустимо, у вас є дві версії класів доступу до вашої бази даних: одна версія, яка входила в систему агресивно і робила багато перевірки, яку ви використовували під час розробки, і інша версія без входу в систему або перевірки, що кричало швидко для виробництва. Приємно мати можливість перемикатися між ними в одному місці. З іншого боку, це досить тривіальна проблема, яку легко вирішувати більш простим способом без складності контейнерів IoC.

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


81
Думаю, у вас ваші факти трохи переплутали Джоеля. Спершу прийшли IoC та контейнери, потім вийшов трактат Мартіна, а не навпаки.
Блок Глена

55
еш більшість контейнерів дозволяють вам їхати дуже швидко. Завдяки автофаку, структури структури, єдності, ninject тощо ви повинні мати можливість контейнера працювати приблизно за 5 хвилин. Так, вони мають розширені функції, але вони вам не потрібні, щоб підніматися з землі.
Блок Глена

58
Джоель, я думав так само, як і ти. Тоді я буквально і серйозно витратив п’ять хвилин, щоб зрозуміти, як запустити найпростіші налаштування, і був вражений правилом 80/20 в дії. Це робить код набагато чистішим, особливо в простих випадках.
Джастін Бозоньє

55
Я займаюся програмуванням менше двох років, і я прочитав вікі Ninject і отримав його. Я з тих пір використовую DI у багатьох місцях. Ти це бачив? Менше двох років і читати кілька сторінок на вікі. Я не чарівник чи нічого. Я не вважаю себе генієм, але мені це було легко зрозуміти. Я не можу уявити, щоб хтось, хто насправді розуміє ОО, не зміг зрозуміти це. Крім того, які посібники з 200-300 сторінок ви посилаєтесь? Я ніколи цього не бачив. Усі контейнери IoC, які я використав, досить короткі і до речі.
JR Garcia

35
+1 добре написано та продумано. Я думаю, що багато людей не усвідомлюють, що додавання в рамки чи інше, щоб "магічно" обробити щось, автоматично додає складності та обмежень системі. Іноді це варто, але часто щось не помічається, і в код виходить ентропія, намагаючись виправити проблему, застосовану обмеженнями. Це може заощадити час наперед, але люди повинні усвідомити, що це може коштувати в 100 разів вниз, намагаючись виправити якусь незрозумілу проблему, лише жменька людей справді має достатньо знань для вирішення.
kemiller2002

37

Імовірно, ніхто не змушує вас використовувати рамку контейнерів DI. Ви вже використовуєте DI для деакуляції своїх занять та поліпшення перевірок, тому ви отримуєте багато переваг. Коротше кажучи, ви віддаєте перевагу простоті, що взагалі є хорошою справою.

Якщо ваша система досягає рівня складності, коли керівництво DI стає рукоділлям (тобто збільшує обслуговування), зважте це на противагу кривій навчання колективної структури контейнерів DI.

Якщо вам потрібен більше контроль над управлінням життям залежностей (тобто якщо ви відчуваєте потребу в реалізації схеми Singleton), подивіться на контейнери DI.

Якщо ви використовуєте контейнер DI, використовуйте лише необхідні функції. Пропустіть файл конфігурації XML і налаштуйте його в коді, якщо цього достатньо. Дотримуйтесь впорскування конструктора. Основи Unity або StructureMap можна скоротити до пари сторінок.

Про це чудова публікація в блозі Марка Семана: Коли використовувати контейнер DI


Дуже хороша відповідь. Єдине, на що я розходився б у думках, - це те, чи можна ручну ін’єкцію EVER вважати менш складною чи не є справою.
wekempf

+1. Один з найкращих відповідей тут. Дякуємо також за посилання на статтю в блозі.
stakx - більше не вносять внесок

1
+1 для збалансованої точки зору. Контейнери IoC не завжди хороші, і вони не завжди погані. Якщо ви не бачите потреби, не використовуйте її. Просто знайте, що інструмент є, якщо ви бачите потребу.
Філ-

32

На мою думку, перевагою номер один в IoC є можливість централізації конфігурації ваших залежностей.

Якщо ви зараз використовуєте ін'єкційну залежність, ваш код може виглядати приблизно так

public class CustomerPresenter
{
  public CustomerPresenter() : this(new CustomerView(), new CustomerService())
  {}

  public CustomerPresenter(ICustomerView view, ICustomerService service)
  {
    // init view/service fields
  }
  // readonly view/service fields
}

Якщо ви використовували статичний клас IoC, на відміну від IMHO, більш заплутаних, конфігураційних файлів, у вас може бути щось подібне:

public class CustomerPresenter
{
  public CustomerPresenter() : this(IoC.Resolve<ICustomerView>(), IoC.Resolve<ICustomerService>())
  {}

  public CustomerPresenter(ICustomerView view, ICustomerService service)
  {
    // init view/service fields
  }
  // readonly view/service fields
}

Тоді, ваш клас Static IoC виглядав би так, я тут використовую Unity.

public static IoC
{
   private static readonly IUnityContainer _container;
   static IoC()
   {
     InitializeIoC();
   }

   static void InitializeIoC()
   {
      _container = new UnityContainer();
      _container.RegisterType<ICustomerView, CustomerView>();
      _container.RegisterType<ICustomerService, CustomerService>();
      // all other RegisterTypes and RegisterInstances can go here in one file.
      // one place to change dependencies is good.
   }
}

11
Бен, те, про що ти говориш, це насправді Місце обслуговування, а не введення залежностей - є різниця. Я завжди віддаю перевагу першому, ніж останньому. stevenharman.net/blog/archive/2009/09/25/…
stevenharman

23
Замість того, щоб робити IoC.Resolve <T> у своєму конструкторі за замовчуванням, слід скористатися здатністю контейнера IOC побудувати об'єкт для вас. Позбавтеся від цього порожнього конструктора і дозвольте контейнеру IOC створити об’єкт для вас. var presenter = IoC.Resolve <CustomerPresenter> (); вуаля! всі залежності вже пов'язані.
Бен Шейрман

12
Недоліком централізації такого типу конфігурації є деконтекстуалізація знань. Джоел вказує на цю проблему, кажучи, "код стає, чесно кажучи, набагато складніше читати".
Скотт Беллвер

6
@sbellware, які знання? Інтерфейс, що приймається як первинна залежність, виступає як договір, і його повинно вистачити для того, щоб передати його мету та поведінку, ні? Я припускаю, що ви можете посилатися на знання, здобуті в результаті вивчення контракту, і в цьому випадку вам потрібно буде відстежити відповідну конфігурацію? Я покладаюся на комп'ютер (R # в VS, IntelliJ тощо), тому що вони чудово підходять для навігації по вузлах та краях графіка; Я зберігаю стек свого мозку для контексту робочого сайту, на якому я зараз зосереджений.
stevenharman

7
Стів, я знаю текст .NET і ходив по цій прогулянці багато років. Я також інтимно знайомий з іншими режимами та формами, і я розумію, що існують справжні компроміси. Я знаю, як і коли робити ці розправи, але різноманітність у моєму робочому житті дозволяє мені відчутно зрозуміти перспективу Джоеля. Замість того, щоб читати мені інструменти та хитрощі, якими я користуюся довше, ніж більшість монокультурників .NET, скажіть мені те, чого я не знаю. Я зарезервую свій набір мозку для різноманітного критичного мислення, а не застою alt.net та ортодоксальності.
Скотт Беллвер

32

IoC контейнери також хороші для завантаження глибоко вкладених залежностей класу. Наприклад, якщо у вас був такий код, використовуючи Depedency Injection.

public void GetPresenter()
{
    var presenter = new CustomerPresenter(new CustomerService(new CustomerRepository(new DB())));
}

class CustomerPresenter
{
    private readonly ICustomerService service;
    public CustomerPresenter(ICustomerService service)
    {
        this.service = service;
    }
}

class CustomerService
{
    private readonly IRespository<Customer> repository;
    public CustomerService(IRespository<Customer> repository)
    {
        this.repository = repository;
    }
}

class CustomerRepository : IRespository<Customer>
{
    private readonly DB db;
    public CustomerRepository(DB db)
    {
        this.db = db;
    }
}

class DB { }

Якщо у вас були завантажені всі ці залежності та контейнер IoC, ви могли б вирішити CustomerService, і всі дочірні залежності автоматично вирішаться.

Наприклад:

public static IoC
{
   private IUnityContainer _container;
   static IoC()
   {
       InitializeIoC();
   }

   static void InitializeIoC()
   {
      _container = new UnityContainer();
      _container.RegisterType<ICustomerService, CustomerService>();
      _container.RegisterType<IRepository<Customer>, CustomerRepository>();
   }

   static T Resolve<T>()
   {
      return _container.Resolve<T>();
   }
}

public void GetPresenter()
{
   var presenter = IoC.Resolve<CustomerPresenter>();
   // presenter is loaded and all of its nested child dependencies 
   // are automatically injected
   // -
   // Also, note that only the Interfaces need to be registered
   // the concrete types like DB and CustomerPresenter will automatically 
   // resolve.
}

Але клас IoC називається за допомогою анти-шаблону службового локатора. Чи слід передавати_контейнер за допомогою конструктора DI замість виклику статичного T Resolve <T> ()?
Майкл Фрейджім

@bendewey Ваш приклад означає, що він автоматично передає аргументи конструкторам для нового CustomerService та нового CustomerRepository. Це так працює? Що робити, якщо у вас є й інші додаткові параметри?
Brain2000

28

Я прихильник декларативного програмування (подивіться, на скільки я відповідаю на питання SQL), але IoC контейнери, на які я дивився, здаються занадто прихованими для їхнього блага.

... або, можливо, розробники контейнерів IoC не здатні написати чітку документацію.

... а то й інше вірно в тій чи іншій мірі.

Я не думаю, що концепція контейнера IoC погана. Але впровадження має бути одночасно потужним (тобто гнучким), щоб бути корисним у широкому спектрі застосувань, але при цьому просто і легко зрозуміти.

Це, мабуть, шість з одного і півдесятка іншого. Справжня програма (не іграшка чи демонстрація) повинна бути складною, що обчислює безліч кутових випадків та винятків із правил. Або ви інкапсулює цю складність в імперативному коді, або в декларативному коді. Але ви повинні десь представляти це.


5
Мені подобається ваше спостереження про неминучу наявність складності. Різниця між проводкою на основі контейнерів та ручним залежність полягає в тому, що, використовуючи контейнер, ви можете орієнтуватися на кожен компонент окремо і таким чином керувати збільшенням складності досить лінійним способом. Системи з ручним провідником залежностей розвиваються складніше, оскільки акт налаштування одного компонента важко ізолювати від налаштування всього іншого.
Ніколас Блюмхардт

Мені подобається AspectJ. Це не Java, але і це не XML. Це свого роду IS Java, оскільки він відчуває і схожий на Java, будучи розширенням її. Я вважаю за краще уникати своїх аспектів у своїх POJO та у своїй бізнес-логіці. Я майже хотів би також утримати свої ІОК. Цілком багато XML є в підключенні інструментів, IMHO. Якщо мені доведеться мати конфігураційні файли, нехай це будуть сценарії BeanShell. / Java, застосуйте тут свої .Net workalikes ->.
Кріс К

2
Я, безумовно, розумію вашу відповідь, але я думаю, що значна частина проблеми полягає в тому, що багато фреймворків IoC (і, справді, багато інших фреймворків для інших речей) також описані та задокументовані для інших, вже добре знайомих з необхідні поняття та термінологія. Я багато про це дізнаюся, щойно успадкував інший код програміста. Я намагаюся зрозуміти, чому в коді використовується IoC, коли "графіки об'єктів" досить малі, і реального тестового покриття коду немає. Як програміст SQL, ви, ймовірно, можете краще оцінити використання IoC з ORM.
Кенні Евітт

1
@KennyEvitt, добре, здається, передчасно використовувати IoC-фреймворк, якщо у нього є просте додаток і навіть не заважають мати хороший тестовий покрив.
Білл Карвін

27

Використання контейнера полягає в основному про зміну від імперативного / сценарію стилю ініціалізації та конфігурації на декларативний . Це може мати кілька різних корисних ефектів:

  • Зменшення процедур запуску основної програми hairball .
  • Включення досить глибоких можливостей відновлення часу розгортання
  • Зробити стиль, що вводить залежність, шлях найменшого опору для нової роботи.

Звичайно, можуть виникнути труднощі:

  • Код, який вимагає складного управління запуском / відключенням / життєвим циклом, може бути не легко адаптований до контейнера.
  • Ймовірно, вам доведеться орієнтуватися на будь-які особисті проблеми, процеси та командну культуру - але тоді, тому ви запитали ...
  • Деякі з наборів інструментів швидко набувають важку вагу, що заохочує таку глибоку залежність, від якої багато контейнерів DI розпочалися як люфт.

23

Мені здається, ви вже створили свій власний контейнер IoC (використовуючи різні шаблони, описані Мартіном Фаулером) і запитуєте, чому чиясь реалізація краща за вашу.

Отже, у вас є купа коду, який вже працює. І вам цікаво, чому ви хочете замінити його на чиюсь реалізацію.

Плюси розгляду сторонніх контейнерів IoC

  • Ви можете виправляти помилки безкоштовно
  • Дизайн бібліотеки може бути кращим, ніж ваш
  • Люди, можливо, вже знайомі з конкретною бібліотекою
  • Бібліотека може бути швидшою, ніж ваша
  • У ньому можуть бути деякі функції, які ви хочете реалізувати, але ніколи не встигали (у вас є локатор обслуговування?)

Мінуси

  • Ви отримуєте помилки безкоштовно :)
  • Дизайн бібліотеки може бути гіршим, ніж ваш
  • Ви повинні вивчити новий API
  • Занадто багато функцій, які ви ніколи не будете використовувати
  • Зазвичай важче налагодити код, який ви не написали
  • Міграція з попереднього контейнера IoC може бути втомливою

Отже, зважте свої плюси проти мінусів і прийміть рішення.


Я погоджуюся з вами. Сем - DI - це тип IoC, тому якщо оригінальний плакат робить DI, вони вже роблять IoC, тому справжнє питання - навіщо виконувати свій власний.
Джеймі Лав

3
Я не погоджуюсь. МОК - це спосіб робити DI. Контейнери МОК роблять DI, беручи під контроль типовий тип, використовуючи динамічне відображення часу виконання для задоволення та введення залежностей. ОП пропонує, що він робить статичний DI, і розмірковує над тим, чому йому потрібно скасовувати переваги перевірки часу компіляції на переваги, часто пов'язані з динамічним відображенням часу виконання МОК.
Maxm007

17

Я думаю, що більшість цінностей IoC отримано за допомогою DI. Оскільки ви вже це робите, решта вигоди збільшується.

Отримане значення буде залежати від типу програми, над якою ви працюєте:

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

  • Маючи багато точок розширюваності, IoC можна використовувати для завантаження компонентів із конфігурації. Це звична річ, але інструменти надаються контейнером.

  • Якщо ви хочете використовувати AOP для деяких наскрізних проблем, IoC надає гачки для перехоплення викликів методу. Це рідше робиться спеціальними проектами, але ІОК робить це простішим.

Я писав подібні функції раніше, але якщо мені потрібна будь-яка з цих функцій, я б скоріше скористався заздалегідь випробуваним інструментом, якщо він відповідає моїй архітектурі.

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

Я використовую контейнер IoC і ціную функціональність, але мушу визнати, що я помітив компроміс: Мій код стає більш чітким на рівні класу і менш чітким на рівні програми (тобто візуалізує потік управління).


16

Я наркоман МОК, що одужує. Мені зараз важко виправдати використання IOC для DI в більшості випадків. Жертви контейнерів МОК збирають перевірку часу і нібито взамін дають вам "просту" настройку, складне управління життям та на ходу виявлення залежностей під час виконання. Я вважаю, що втрата перевірки часу компіляції і магія / винятки, що виникають в результаті виконання часу, не вартують дзвіночків у переважній більшості випадків. У великих корпоративних програмах їм може бути дуже важко слідкувати за тим, що відбувається.

Я не купую аргумент централізації, тому що ви можете легко централізувати статичну настройку, також використовуючи абстрактну фабрику для свого застосування та релігійно відкладаючи створення об'єктів на абстрактну фабрику, тобто робіть належний DI.

Чому б не зробити статичний DI без магії, як це:

interface IServiceA { }
interface IServiceB { }
class ServiceA : IServiceA { }
class ServiceB : IServiceB { }

class StubServiceA : IServiceA { }
class StubServiceB : IServiceB { }

interface IRoot { IMiddle Middle { get; set; } }
interface IMiddle { ILeaf Leaf { get; set; } }
interface ILeaf { }

class Root : IRoot
{
    public IMiddle Middle { get; set; }

    public Root(IMiddle middle)
    {
        Middle = middle;
    }

}

class Middle : IMiddle
{
    public ILeaf Leaf { get; set; }

    public Middle(ILeaf leaf)
    {
        Leaf = leaf;
    }
}

class Leaf : ILeaf
{
    IServiceA ServiceA { get; set; }
    IServiceB ServiceB { get; set; }

    public Leaf(IServiceA serviceA, IServiceB serviceB)
    {
        ServiceA = serviceA;
        ServiceB = serviceB;
    }
}


interface IApplicationFactory
{
    IRoot CreateRoot();
}

abstract class ApplicationAbstractFactory : IApplicationFactory
{
    protected abstract IServiceA ServiceA { get; }
    protected abstract IServiceB ServiceB { get; }

    protected IMiddle CreateMiddle()
    {
        return new Middle(CreateLeaf());
    }

    protected ILeaf CreateLeaf()
    {
        return new Leaf(ServiceA,ServiceB);
    }


    public IRoot CreateRoot()
    {
        return new Root(CreateMiddle());
    }
}

class ProductionApplication : ApplicationAbstractFactory
{
    protected override IServiceA ServiceA
    {
        get { return new ServiceA(); }
    }

    protected override IServiceB ServiceB
    {
        get { return new ServiceB(); }
    }
}

class FunctionalTestsApplication : ApplicationAbstractFactory
{
    protected override IServiceA ServiceA
    {
        get { return new StubServiceA(); }
    }

    protected override IServiceB ServiceB
    {
        get { return new StubServiceB(); }
    }
}


namespace ConsoleApplication5
{
    class Program
    {
        static void Main(string[] args)
        {
            var factory = new ProductionApplication();
            var root = factory.CreateRoot();

        }
    }

    //[TestFixture]
    class FunctionalTests
    {
        //[Test]
        public void Test()
        {
            var factory = new FunctionalTestsApplication();
            var root = factory.CreateRoot();
        }
    }
}

Конфігурація контейнера - це ваша абстрактна фабрична реалізація, ваші реєстрації - це реалізація абстрактних членів. Якщо вам потрібна нова одиночна залежність, просто додайте ще одну абстрактну властивість до абстрактної фабрики. Якщо вам потрібна перехідна залежність, просто додайте інший метод і введіть його як функцію <>.

Переваги:

  • Вся конфігурація налаштування та створення об'єктів централізована.
  • Конфігурація - це просто код
  • Перевірка часу компіляції полегшує технічне обслуговування, оскільки ви не можете забути оновити свої реєстрації.
  • Ніякої магії відображення під час роботи

Я рекомендую скептикам, щоб перейти до наступного проекту зеленого поля і чесно запитати себе, в який момент вам потрібен контейнер. Пізніше, якщо ви просто замінюєте заводську реалізацію модулем конфігурації контейнера IOC, його легко розмістити в контейнері IOC.


Це не здається мені аргументом проти IoC, а аргументом проти налаштованих рамок IoC. Чи не тут ваші заводи просто зводяться до простих жорстко закодованих ІОК?
Містер Міндор

Я думаю, коли плакат згадав про IoC, контейнерну рамку IoC. Дякую за оригінальний запис, це допомагає переконати мене йти без рамки. Отже, якщо це налаштовується в тестах і у виробничому коді, то яка перевага "настроюваної" рамки?
huggie

З мого розуміння термінології, Inversion Of Control означає пошук залежностей під час виконання. Так, так, можна сказати, фабрика - це в основному жорсткий або статичний контейнер, але це не контейнер МОК. Типи вирішуються під час компіляції. Це аргумент проти використання настроюваних фреймворків, які використовують пошук у словнику для вирішення типів під час виконання.
Maxm007

14

Найбільша перевага використання контейнерів IoC для мене (особисто я використовую Ninject) полягала в тому, щоб усунути обхід налаштувань та інших видів глобальних державних об'єктів.

Я не програмую для Інтернету, моя - це консольна програма, і в багатьох місцях, що знаходяться в дереві об'єктів, мені потрібно мати доступ до налаштувань або метаданих, визначених користувачем, створених на абсолютно окремій гілці дерева об'єктів. За допомогою IoC я просто кажу Ninject ставитись до Налаштувань як до однотонних (тому що завжди є лише один екземпляр), запитайте Налаштування чи Словник у конструкторі та престорі ... вони магічно з'являються, коли мені вони потрібні!

Не використовуючи контейнер IoC, я повинен був би передати налаштування та / або метадані вниз через 2, 3, ..., n об'єктів, перш ніж він був фактично використаний об'єктом, який його потребував.

У контейнерів DI / IoC є багато інших переваг, оскільки інші люди детально описали тут, і перехід від ідеї створення об'єктів до запитування об'єктів може бути вигинним, але використання DI було дуже корисним для мене та моєї команди, тому, можливо, ви можете додати його до вашого арсеналу!


2
Це величезний плюс. Не можу повірити, що ніхто раніше не згадував про це. Без необхідності передавати або зберігати тимчасові об’єкти робить код набагато чистішим.
Фінглас

1
@qble глобальні об'єкти працюють чудово ... якщо ви хочете, щоб ваша база коду була щільно зв'язаним, неперевіреним кошмаром, який стає важче і складніше змінювати з часом. удачі з цим.
Джефрі Кемерон

2
@JeffreyCameron IoC Container є глобальним об'єктом. Це не знімає ваших глобальних залежностей.
qble

3
Правда, проте контейнер IoC прозорий для решти програми. Ви закликаєте його один раз при запуску, щоб отримати екземпляр, і тоді це останнє, що ви бачили. З глобальними об'єктами ваш код засмічений важкими залежностями
Джефрі Кемерон

1
@qble використовують фабрики для створення перехідних примірників на ходу. Заводи - це одноосібні послуги. Якщо тимчасові екземпляри потребують чогось із контейнера МОК, тоді проводять їх на завод і передають фабриці передати залежність в перехідний екземпляр.
Джефрі Кемерон

14

Рамки IoC відмінні, якщо ви хочете ...

  • ... викинути безпеку типу. Багато (усі?) Рамки IoC змушують вас виконувати код, якщо ви хочете бути впевненими, що все підключено правильно. "Гей! Сподіваюся, у мене все було налаштовано, щоб мої ініціалізації цих 100 класів не закінчилися у виробництві, кидаючи винятки з нульовими покажчиками!"

  • ... послід ваш код з глобальний (рамки IoC є все про мінливих глобальних станів).

  • ... пишіть хитромудрий код із незрозумілими залежностями, які важко переробити, оскільки ви ніколи не дізнаєтесь, що від чого залежить.

Проблема IoC полягає в тому, що люди, які їх використовують, писали такий код

public class Foo {
    public Bar Apa {get;set;}
    Foo() {
        Apa = new Bar();
    }
}

що, очевидно, недоліки, оскільки залежність між Фо і Бар є провідним. Тоді вони зрозуміли, що було б краще написати подібний код

public class Foo {
    public IBar Apa {get;set;}
    Foo() {
        Apa = IoC<IBar>();
    }
}

що також є недоліком, але менш очевидно. У Haskell тип Foo()буде, IO Fooале ви дійсно не хочете, щоб IOчастина була, і це має бути попереджувальним знаком, що з вашим дизайном щось не так, якщо ви його отримали.

Щоб позбутися від нього (IO-частини), отримайте всі переваги IoC-каркасів, і жоден з його недоліків ви можете замість цього використовувати абстрактну фабрику.

Правильне рішення було б щось подібне

data Foo = Foo { apa :: Bar }

або можливо

data Foo = forall b. (IBar b) => Foo { apa :: b }

і вводити (але я б не називав це введення).

Також: дивіться це відео з Еріком Мейєром (винахідником LINQ), де він говорить, що DI призначений для людей, які не знають математики (і я не міг би погодитися): http://www.youtube.com/watch?v = 8Mttjyf-8P4

На відміну від пана Спольського, я не вірю, що люди, які використовують IoC-рамки, дуже розумні - я просто вважаю, що вони не знають математики.


9
викиньте безпеку типу - окрім вашого прикладу, все-таки безпечний тип, ось такий інтерфейс. Ви обходите не типи об’єктів, а тип інтерфейсу. І безпека типу все одно не усуває нульові посилання на винятки, ви можете раді передавати нульові посилання як безпечні параметри типу - це взагалі не проблема безпеки типу.
blowdart

Ви викидаєте безпеку типу, оскільки не знаєте, підключений чи ні в контейнері IoC (тип IoC <IBar> ні IBar, це насправді IO IBar) .. І так, у прикладі Haskell Я не можу отримати нульове посилання.
фінсон

Безпека введення насправді не в тому, що об’єкт підключається, принаймні не в Java / C #, а просто в тому, що об'єкт потрібного типу. І ConcreteClass myClass = null все-таки правильного типу. Якщо ви хочете примусово виконати недійсне значення, тоді кодові контракти вступають у дію.
blowdart

2
Я до певної міри погоджуюся з вашою першою точкою, я хотів би пояснення 2-го і 3-го жодного разу для мене не було проблемою. Ваш зразок C # використовує контейнер IoC як локатор сервісу - поширена помилка. І порівнюючи C # проти Haskell = яблука та апельсини.
Маурісіо Шеффер

2
Ви не відкидаєте безпеку типу. Деякі контейнери DI (якщо не більшість) дозволяють перевірити повний графік об'єкта після процедури реєстрації. Роблячи це, ви можете запобігти запуску програми за допомогою неправильної конфігурації (невдалої швидкості), і у моїх програмах завжди є кілька тестових одиниць, які викликають конфігурацію виробництва, щоб я міг знайти ці помилки під час тестування. Я б порадив усім, хто використовує контейнер IoC, написати декілька тестових одиниць, щоб переконатися, що всі об'єкти верхнього рівня можуть бути вирішені.
Стівен

10

Я виявив, що правильна реалізація Dependency Injection, як правило, змушує програмістів використовувати різноманітні інші методи програмування, які допомагають поліпшити перевірку, гнучкість, ремонтопридатність та масштабованість коду: такі практики, як єдиний принцип відповідальності, розділення проблем та кодування проти API. Схоже, мене змушують писати більш модульні класи та методи розміру, що полегшує читання коду, оскільки його можна приймати в шматки розміру куса.

Але воно також має тенденцію створювати досить великі дерева залежностей, якими набагато легше керувати за допомогою рамки (особливо, якщо ви використовуєте конвенції), ніж вручну. Сьогодні я хотів тестувати щось дуже швидко на LINQPad, і я подумав, що буде занадто непокоїти створити ядро ​​та завантажити в своїх модулях, і я закінчив писати це вручну:

var merger = new SimpleWorkflowInstanceMerger(
    new BitFactoryLog(typeof(SimpleWorkflowInstanceMerger).FullName), 
    new WorkflowAnswerRowUtil(
        new WorkflowFieldAnswerEntMapper(),
        new ActivityFormFieldDisplayInfoEntMapper(),
        new FieldEntMapper()),
    new AnswerRowMergeInfoRepository());

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

Провівши деякий час, вивчаючи відповіді та коментарі на це питання, я переконаний, що люди, які проти використання контейнера IoC, не практикують справжнє введення залежності. Я бачив приклади практик, які зазвичай плутають із введенням залежності. Деякі люди скаржаться на труднощі з "читанням" коду. Якщо все зроблено правильно, переважна більшість кодів має бути ідентичним при використанні DI вручну, як і при використанні контейнера IoC. Різниця повинна повністю знаходитися в декількох "точках запуску" в програмі.

Іншими словами, якщо вам не подобаються контейнери IoC, ви, ймовірно, не робите впорскування залежностей так, як це слід робити.

Ще один момент: введення залежностей справді неможливо зробити вручну, якщо ви використовуєте рефлексію де завгодно. Хоча я ненавиджу те, що відображає кодову навігацію, ви повинні визнати, що є певні області, де цього справді не уникнути. Наприклад, ASP.NET MVC намагається створити контролер за допомогою відображення кожного запиту. Щоб зробити введення залежності вручну, вам доведеться зробити кожен контролер "контекстним корінцем", наприклад:

public class MyController : Controller
{
    private readonly ISimpleWorkflowInstanceMerger _simpleMerger;
    public MyController()
    {
        _simpleMerger = new SimpleWorkflowInstanceMerger(
            new BitFactoryLog(typeof(SimpleWorkflowInstanceMerger).FullName), 
            new WorkflowAnswerRowUtil(
                new WorkflowFieldAnswerEntMapper(),
                new ActivityFormFieldDisplayInfoEntMapper(),
                new FieldEntMapper()),
            new AnswerRowMergeInfoRepository())
    }
    ...
}

Тепер порівняйте це з дозволом рамки DI зробити це для вас:

public MyController : Controller
{
    private readonly ISimpleWorkflowInstanceMerger _simpleMerger;
    public MyController(ISimpleWorkflowInstanceMerger simpleMerger)
    {
        _simpleMerger = simpleMerger;
    }
    ...
}

Використовуючи рамку DI, зауважте, що:

  • Я можу провести тест на цей клас. Створюючи макет ISimpleWorkflowInstanceMerger, я можу перевірити, що він застосовується так, як я передбачаю, без необхідності підключення до бази даних чи нічого.
  • Я використовую набагато менше коду, і код читається набагато простіше.
  • Якщо зміниться одна із залежностей моєї залежності, мені не потрібно вносити жодних змін до контролера. Це особливо приємно, якщо врахувати, що кілька контролерів, ймовірно, використовують одні і ті ж залежності.
  • Я ніколи прямо не посилаюсь на класи зі свого рівня даних. Мій веб-додаток може просто включати посилання на проект, що містить ISimpleWorkflowInstanceMergerінтерфейс. Це дозволяє мені розбити додаток на окремі модулі та підтримувати справжню багаторівневу архітектуру, що, в свою чергу, робить речі набагато гнучкішими.

У типовому веб-додатку буде досить багато контролерів. Весь біль від виконання DI вручну в кожному контролері дійсно збільшиться в міру зростання вашої програми. Якщо у вас є додаток, що має лише один корінь контексту, який ніколи не намагається створити службу шляхом відображення, то це не така вже велика проблема. Тим не менш, будь-яка програма, яка використовує Dependency Injection, стане надзвичайно дорогою для управління, коли вона досягне певного розміру, якщо ви не використовуєте якусь структуру для управління графіком залежності.


9

Щоразу, коли ви використовуєте "нове" ключове слово, ви створюєте конкретну залежність від класу, і у вашій голові повинен пролунати маленький дзвінок тривоги. Випробовувати цей об’єкт стає важче ізольовано. Рішення полягає в запрограмуванні інтерфейсів та введенні залежності, щоб об'єкт міг бути перевірений одиницею з будь-чим, що реалізує цей інтерфейс (наприклад, макети).

Біда в тому, що вам доведеться десь побудувати об’єкти. Заводський зразок - це один із способів змістити з'єднання з ваших POXO (Plain Old "вставити тут свою мову OO" Об'єкти). Якщо ви та ваші колеги пишуть такий код, то контейнер IoC - це наступне «Покращення вдосконалення», яке ви можете зробити у своїй кодовій базі. Це змістить увесь той неприємний заводський код котла з ваших чистих об'єктів та ділової логіки. Вони це отримають і люблять. Чорт забирай, розмовляй з компанією, чому ти це любиш, і всі захоплюються.

Якщо ваші колеги ще не займаються ІР, то я б запропонував вам сфокусуватися на цьому спочатку. Поширити слово про те, як написати чистий код, який легко перевірити. Чистий код DI - найважливіша частина, коли ви вже там, перехід логіки проводки об'єкта з заводських класів на контейнер IoC повинен бути відносно тривіальним.


8

Оскільки всі залежності чітко видно, це сприяє створенню компонентів, які слабко з'єднані, і в той же час легко доступні та багаторазові.


7

Вам не потрібен контейнер IoC.

Але якщо ви суворо дотримуєтесь схеми DI, ви виявите, що наявність одного видалить тону зайвого, нудного коду.

Це, найчастіше, найкращий час для використання бібліотеки / фреймворку - коли ви розумієте, що це робить, і можете це зробити без бібліотеки.


6

Мені просто так трапляється витягувати домашній код DI та замінювати його МОК. Я, ймовірно, видалив понад 200 рядків коду і замінив його приблизно 10. Так, мені довелося трохи навчитися користуватися контейнером (Winsor), але я інженер, що працює над інтернет-технологіями в 21 століття, тому я звик до цього. Я, певно, витратив близько 20 хвилин на перегляд того, як tos. Це добре вартувало мого часу.


3
"але я інженер, що працюю над інтернет-технологіями у 21 столітті, тому я звик до цього" -> +1
user96403

5

Поки ви продовжуєте роз'єднувати свої класи та інвертувати свої залежності, класи продовжують залишатися невеликими, а "графік залежності" продовжує збільшуватися в розмірах. (Це не погано.) Використання основних функцій контейнера IoC робить проводку всіх цих об'єктів дрібницею, але робити це вручну можна дуже обтяжливо. Наприклад, що робити, якщо я хочу створити новий екземпляр "Foo", але йому потрібна "Bar". І "Бар" потребує "А", "В" і "С". І кожному з них потрібні 3 інші речі і т. Д. І т. Д. (Так, я не можу придумати хороших підроблених імен :)).

Використання контейнера IoC для побудови вашого об'єктного графіка зменшує складність тонни і виштовхує його в одноразову конфігурацію. Я просто кажу "створи мені" Foo ", і він з'ясовує, що потрібно для його створення.

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


1
чому ми продовжуємо оскаржувати найменший загальний знаменник, а не працюємо над їх підняттям?
stevenharman

4
Я нічого не сказав про приємність. Я щойно сказав, що вдосконалені сценарії можуть зробити щось більш заплутаним для нових дисков - це факт. Я не сказав "так не робіть цього". Але навіть тоді (час для захисника диявола) часто існують інші способи досягти того самого, що робить кодову базу більш чіткою та доступною. Приховувати складність у власному конфігурації інструменту інфраструктури лише тому, що ви не можете слушною причиною, і це нікого не підтягує.
Аарон Лерч

4

Дітто про Єдність. Станьте занадто великим, і ви зможете почути скрип на кроквах.

Мене ніколи не дивує, коли люди починають носити інформацію про те, як чистий IoC-код виглядає - це ті самі люди, які свого часу говорили про те, як шаблони в C ++ були елегантним способом повернутися в 90-ті, але в наш час вони розшифрують їх як таємницю . Ба!


2

У світі .NET AOP не надто популярний, тому для DI рамки є вашим єдиним реальним варіантом, чи ви пишете один, чи використовуєте інший фреймворк.

Якщо ви використовували AOP, ви можете вводити його під час складання програми, що є більш поширеним у Java.

Існує багато переваг для DI, таких як зменшене з'єднання, тому тестування одиниці простіше, але як ви це реалізуєте? Ви хочете використовувати рефлексію, щоб зробити це самостійно?


Нова рамка MEF дозволяє саме це для .NET, і я мушу сказати, що код чистіший і простіший для розуміння, значно полегшує розуміння поняття DI.
terjetyl

4
@TT: MEF не є контейнером для ін'єкцій залежностей і не має нічого спільного з AOP.
Маурісіо Шеффер

2

Отже, минуло майже 3 роки, так?

  1. 50% тих, хто прокоментував рамки IoC, не розуміють різниці між IoC та IoC Frameworks. Сумніваюсь, вони знають, що ви можете писати код, не розгортаючись на сервер додатків

  2. Якщо взяти найпопулярніший фреймворк Java Spring, конфігурація IoC переміщена з XMl в код і тепер виглядає так

`@Configuration public class AppConfig {

public @Bean TransferService transferService() {
    return new TransferServiceImpl(accountRepository());
}

public @Bean AccountRepository accountRepository() {
    return new InMemoryAccountRepository();
}

} `І для цього нам потрібна рамка, чому саме?


1

Чесно кажучи, я не знаю, що є багато випадків, коли потрібні контейнери IoC, і більшість часу вони просто додають зайвих складностей.

Якщо ви використовуєте його просто для спрощення побудови об'єкта, я б запитав, чи інстанціюєте цей об'єкт у кількох місцях? Невже одинак ​​не відповідає вашим потребам? Ви змінюєте конфігурацію під час виконання? (Перемикання типів джерел даних тощо).

Якщо так, то вам може знадобитися контейнер IoC. Якщо ні, то ви просто переміщуєте ініціалізацію туди, де розробник може її легко побачити.

Хто сказав, що інтерфейс краще, ніж успадкування? Скажіть, ви тестуєте службу. Чому б не використовувати конструктор DI, а не створювати макети залежностей, використовуючи спадкування? Більшість сервісів, якими я користуюся, мають лише кілька залежностей. Виконання блоку тестування Таким чином , запобігає зберігши масу непотрібних інтерфейсів і коштів не має використовувати Resharper для швидкого пошуку декларації методу.

Я вважаю, що для більшості реалізацій сказати, що IoC-контейнери видаляють непотрібний код - це міф.

По-перше, налаштування контейнера в першу чергу. Тоді вам залишається визначити кожен об'єкт, який потрібно ініціалізувати. Таким чином, ви не зберігаєте код при ініціалізації, ви переміщуєте його (якщо ваш об'єкт не використовується більше одного разу. Це краще як Singleton?). Потім для кожного об'єкта, який ви ініціалізували таким чином, ви повинні створити та підтримувати інтерфейс.

У когось є думки з цього приводу?


Навіть для невеликих проектів введення конфігурації в XML робить її набагато більш чистою та модульною - і вперше робить код багаторазовим (оскільки він більше не забруднений клеєм). Для належних програмних продуктів, ви можете налаштувати чи заміни, наприклад , ShippingCostCalculatorабо ProductUIPresentation, або будь-яку іншу стратегію / реалізації, в тому ж коді , розгорнувши тільки один змінений файл XML.
Thomas W

Якщо це реалізується вольово-невольно, як загальна практика, я думаю, ви додасте більше складності, а не менше. Коли вам потрібно щось поміняти під час виконання, вони чудові, але навіщо додавати заплутаність? Навіщо зберігати накладні витрати додаткових інтерфейсів для речей, які ніколи не будуть вимикатися? Я не кажу, що не використовуйте IoC для тестування. У будь-якому разі використовуйте властивість або навіть конструктор залежностей від конструктора. Чи потрібно використовувати інтерфейс? Чому б не підкласировать тестові об’єкти? Чи не було б це чистіше?
Фред

Вам зовсім не потрібні інтерфейси, щоб зробити конфігурацію XML / IoC. Я знайшов значні покращення в ясності в невеликому проекті, що має лише ~ 8 сторінок і 5 або 6 сервісів. Тут менше затуманення, оскільки службам та контролерам більше не потрібен код клею.
Thomas W

1

Вам потрібен контейнер IoC, якщо вам потрібно буде централізувати конфігурацію ваших залежностей, щоб вони могли бути легко замінені масово. Це має найбільше сенс у TDD, де багато залежностей замінено, але де мало взаємозалежності між залежностями. Це робиться ціною певної міри ослаблення потоку управління об'єктом будівництва, тому важливо мати добре організовану та обґрунтовану документацію конфігурації. Це також добре мати причину для цього, інакше це просто абстрагування позолотою . Я бачив, що це робилося настільки погано, що його перетягували на еквівалент твердження goto для конструкторів.


1

Ось чому. Проект називається IOC-with-Ninject. Ви можете завантажити та запустити його за допомогою Visual Studio. У цьому прикладі використовується Ninject, але ВСІ "нові" заяви знаходяться в одному місці, і ви можете повністю змінити спосіб роботи програми, змінивши модуль прив'язки. Приклад налаштований так, що ви можете прив’язати до насмішкуваної версії послуг або реальної версії. У малих проектах, які можуть не мати значення, але у великих проектах це велика справа.

Щоб було зрозуміло, переваги, як я їх бачу: 1) ВСІ нові заяви в одному місці в корені коду. 2) Повністю рефакторний код з однією зміною. 3) Додаткові бали за "класний фактор", тому що це ... добре: круто. : с


1

Я спробую знайти, чому з моєї точки зору МОК може бути корисним.

Як і у всьому іншому, контейнер IOC (або, як би сказав Ейнштейн I = OC ^ 2) - це концепція, яку ви повинні вирішити для себе, вам це потрібно чи ні у вашому коді. Останні виклики моди щодо МОК - це лише це, мода. Не впадайте в моду, це перше. Існує безліч концепцій, які ви могли реалізувати у своєму коді. Перш за все, я використовую ін'єкцію залежності від того, як почав програмувати, і засвоїв сам термін, коли він популяризувався під цією назвою. Контроль залежності - дуже стара тема, і до цього часу його вирішували трильйонами способів, залежно від того, що було відірвано від чого. Розв’язувати все від усього - це нісенітниця. Проблема контейнера IOC полягає в тому, що він намагається бути настільки ж корисним, як Entity Framework або NHibernate. Хоча написання об'єктно-реляційного картографа є просто необхідним, як тільки вам доведеться з'єднати будь-яку базу даних зі своєю системою, контейнер IOC не завжди необхідний. Отже, коли контейнер IOC корисний:

  1. Коли у вас є ситуація з багатьма залежностями, які ви хочете організувати
  2. Коли вам не байдуже зв’язувати свій код із стороннім продуктом
  3. Коли ваші розробники хочуть навчитися працювати з новим інструментом

1: Це не так часто, що у вас є стільки залежностей у вашому коді, або що ви знаєте про них на початку дизайну. Абстрактне мислення корисно, коли абстрактне мислення належне.

2: З'єднання вашого коду з стороннім кодом - це проблема HuGe. Я працював з кодом, якому більше 10 років, і в той час слідкували за фантазійними та передовими концепціями ATL, COM, COM + тощо. З цим кодом зараз нічого не можна зробити. Я говорю про те, що вдосконалена концепція дає очевидну перевагу, але це скасовується в довгостроковій перспективі із застарілою перевагою. Це просто зробило все це дорожчим.

3: Розробка програмного забезпечення досить важка. Ви можете розширити його до невпізнанних рівнів, якщо ви дозволите, щоб якась передова концепція врізалася у ваш код. Виникла проблема з IOC2. Хоча це і розв'язує залежності, але і розв'язує логічний потік. Уявіть, що ви знайшли помилку і вам потрібно встановити перерву, щоб вивчити ситуацію. IOC2, як і будь-яка інша передова концепція, робить це складніше. Виправити помилку в рамках концепції складніше, ніж виправити помилку в більш простому коді, тому що, коли виправляєте помилку, концепція повинна повторюватися знову. (Просто для прикладу, C ++ .NET постійно змінює синтаксис настільки сильно, що вам потрібно добре подумати, перш ніж переробляти якусь стару версію .NET.) Так у чому проблема з IOC? Проблема полягає у вирішенні залежностей. Логіка вирішення зазвичай прихована в самому IOC2, написано, можливо, нечасто, що вам потрібно вчитися та підтримувати. Чи буде ваш сторонній продукт через 5 років? Microsoft не була.

"Ми знаємо, як" синдром написаний повсюдно щодо IOC2. Це схоже на тестування автоматизації. Фантастичний термін та ідеальне рішення на перший погляд, ви просто складете всі свої тести, які потрібно виконати протягом ночі, і побачите результати вранці. Дуже боляче пояснювати компанії після компанії, що насправді означає автоматизоване тестування. Автоматизоване тестування, безумовно, не швидкий спосіб зменшити кількість помилок, які ви можете впровадити протягом ночі, щоб підвищити якість вашого продукту. Але мода робить це поняття дратівливо домінуючим. IOC2 страждає тим же синдромом. Вважається, що вам потрібно реалізувати це для того, щоб ваше програмне забезпечення було хорошим. Нещодавнє інтерв'ю EvErY мене запитали, чи впроваджую IOC2 та автоматизацію. Це ознака моди: компанія мала частину коду, написаного в MFC, який вони не відмовляються.

Вам потрібно вивчити IOC2, як і будь-яку іншу концепцію програмного забезпечення. Рішення про необхідність використання IOC2 - це команда та компанія. Однак принаймні ВСІ вище наведені аргументи повинні бути згадані перед прийняттям рішення. Тільки якщо ви бачите, що плюс сторони переважає негативну сторону, ви можете прийняти позитивне рішення.

У IOC2 немає нічого поганого, окрім того, що він вирішує лише проблеми, які він вирішує, і вводить проблеми, які він вводить. Більш нічого. Однак піти проти моди дуже важко, у них пітніють рот, послідовники чого завгодно. Дивно, як ніхто з них не виникає, коли проблема їх фантазії стає очевидною. Багато концепцій в галузі індустрії програм було захищено, оскільки вони приносять прибуток, пишуться книги, проводяться конференції, виробляються нові продукти. Це мода, як правило, недовговічна. Як тільки люди знаходять щось інше, вони повністю відмовляються від цього. IOC2 корисний, але він показує ті ж ознаки, що і багато інших зниклих концепцій, які я бачив. Я не знаю, чи виживе. Для цього немає правила. Ви думаєте, якщо це корисно, воно виживе. Ні, це не йде таким шляхом. Однієї великої багатої компанії достатньо, і концепція може загинути протягом декількох тижнів. Побачимо. NHibernate вижив, EF вийшов другим. Можливо, IOC2 теж виживе. Не забувайте, що більшість понять у розробці програмного забезпечення - це ні про що особливе, вони дуже логічні, прості і очевидні, а іноді складніше запам’ятати діючу конвенцію про іменування, ніж зрозуміти саму концепцію. Чи знання IOC2 робить розробника кращим розробником? Ні, тому що якщо розробник не зміг придумати концепцію, подібну за своєю суттю до IOC2, йому буде складно зрозуміти, яку проблему вирішує IOC2, використовуючи її, вона виглядатиме штучно, і він або вона може почати її використовувати заради того, щоб бути якоюсь політично коректною. Не забувайте, що більшість понять у розробці програмного забезпечення - це ні про що особливе, вони дуже логічні, прості і очевидні, а іноді складніше запам’ятати діючу конвенцію про іменування, ніж зрозуміти саму концепцію. Чи знання IOC2 робить розробника кращим розробником? Ні, тому що якщо розробник не зміг придумати концепцію, подібну за своєю суттю до IOC2, йому буде складно зрозуміти, яку проблему вирішує IOC2, використовуючи її, вона виглядатиме штучно, і він або вона може почати її використовувати заради того, щоб бути якоюсь політично коректною. Не забувайте, що більшість понять у розробці програмного забезпечення - це ні про що особливе, вони дуже логічні, прості і очевидні, а іноді складніше запам’ятати діючу конвенцію про іменування, ніж зрозуміти саму концепцію. Чи знання IOC2 робить розробника кращим розробником? Ні, тому що якщо розробник не зміг придумати концепцію, подібну за своєю суттю до IOC2, йому буде складно зрозуміти, яку проблему вирішує IOC2, використовуючи її, вона виглядатиме штучно, і він або вона може почати її використовувати заради того, щоб бути якоюсь політично коректною. Чи знання IOC2 робить розробника кращим розробником? Ні, тому що якщо розробник не зміг придумати концепцію, подібну за своєю суттю до IOC2, йому буде складно зрозуміти, яку проблему вирішує IOC2, використовуючи її, вона виглядатиме штучно, і він або вона може почати її використовувати заради того, щоб бути якоюсь політично коректною. Чи знання IOC2 робить розробника кращим розробником? Ні, тому що якщо розробник не зміг придумати концепцію, подібну за своєю суттю до IOC2, йому буде складно зрозуміти, яку проблему вирішує IOC2, використовуючи її, вона виглядатиме штучно, і він або вона може почати її використовувати заради того, щоб бути якоюсь політично коректною.


0

Особисто я використовую IoC як якусь структурну карту своєї програми (Так, я також віддаю перевагу StructureMap;)). Це полегшує заміну моїх незвичайних реалізацій інтерфейсу реалізаціями Moq під час тестів. Створення тестових налаштувань може бути таким же простим, як зробити новий init-виклик до моєї IoC-системи, замінивши те, який клас є моєю тестовою межею з макетом.

Мабуть, це не те, що там існує IoC, але я вважаю, що я його використовую найбільше ..




0

Я усвідомлюю, що це досить старий пост, але він, здається, все ще є досить активним, і я подумав, що я вніс би кілька пунктів, які ще не згадувалися в інших відповідях.

Я скажу, що я погоджуюся з перевагами введення залежності, але я вважаю за краще будувати об'єкти та керувати ними самостійно, використовуючи шаблон, не схожий на той, який окреслював Maxm007 у своїй відповіді. Я знайшов дві основні проблеми із використанням сторонніх контейнерів:

1) Якщо стороння бібліотека керує терміном експлуатації ваших об'єктів "автоматично", може надати несподівані результати. Ми виявили, що, особливо у великих проектах, ви можете мати набагато більше копій об'єкта, ніж ви очікували, і більше, ніж ви, якби ви керували життєвими циклами вручну. Я впевнений, що це змінюється в залежності від використовуваної рамки, але проблема все ж існує. Це також може бути проблематичним, якщо ваш об’єкт містить ресурси, з'єднання даних тощо, оскільки іноді об’єкт може жити довше, ніж ви очікували. Тому неминуче контейнери IoC, як правило, збільшують використання ресурсів і слід пам’яті програми.

2) Контейнери IoC, на мою думку, є формою "програмування чорної скриньки". Я виявив, що зокрема, наші менш досвідчені розробники схильні їх зловживати. Це дозволяє програмісту не замислюватися про те, як об’єкти повинні ставитись один до одного або як їх роз’єднати, оскільки він надає їм механізм, за допомогою якого вони можуть просто захопити будь-який об’єкт, який вони хочуть, з повітря. Наприклад, може бути вагома причина дизайну, що ObjectA ніколи не повинен знати про ObjectB безпосередньо, а замість створення фабрики чи мосту чи локатора обслуговування, недосвідчений програміст просто скаже: "немає проблем, я просто захоплю ObjectB з контейнера IoC" ". Це насправді може призвести до збільшення об'єднаного об'єкта, що, як передбачається, допоможе запобігти IoC.


-8

Ін'єкція залежності в проект ASP.NET може бути виконана за допомогою декількох рядків коду. Я припускаю, що є деяка перевага використання контейнера, коли у вас є додаток, який використовує кілька передніх кінців і потребує тестових одиниць.


1
ви можете навести приклад? Чим DI відрізняється в ASP.NET порівняно з будь-яким іншим типом додатків?
tofi9
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.