Ioc / DI - Чому я повинен посилатись на всі шари / складання у точці входу програми?


123

(Пов’язане з цим питанням, EF4: Чому створення проксі потрібно вмикати, коли включено ледаче завантаження? ).

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

Якщо я не використовував контейнер DI, мені не довелося б посилатися на бібліотеку EntityFramework у моєму додатку MVC3, лише на мій бізнес-рівень, який би посилався на мій рівень DAL / Repo.

Я знаю, що наприкінці дня всі DLL-файли включаються у папку bin, але моя проблема полягає в тому, щоб явно посилатися на неї через "Додати посилання" у VS, щоб можна було опублікувати WAP з усіма необхідними файлами.


1
Цей уривок із книги Dependency Injection у .NET, другому виданні, є більш досконалою версією відповідей як Марка, так і мене самого. У ньому докладно описано поняття Composite Root, і чому дозволити шлях запуску програми залежати від кожного іншого модуля - це насправді гарна річ.
Стівен

Я прочитав посилання на уривок та главу 1, буду купувати книгу, оскільки мені дуже сподобалися аналогії та прості пояснення до складної справи Д.І. Я думаю, вам слід запропонувати нову відповідь, чітко відповісти "вам не доведеться посилатися на всі шари / склади в логічному шарі введення, якщо це також не ваш корінь композиції", посилання на уривок і розмістити зображення Рисунок 3, з витяг.
diegohb

Відповіді:


194

Якщо я не використовував контейнер DI, мені не довелося б посилатися на бібліотеку EntityFramework у моєму додатку MVC3, лише на мій бізнес-рівень, який би посилався на мій рівень DAL / Repo.

Так, саме така ситуація в DI працює настільки важко, щоб уникнути :)

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

Глибокий графік

Оскільки граф залежностей глибокі, це означає , що більшість бібліотек перетягнути на багато інших залежності - наприклад , на схемі, бібліотека З тягнеться уздовж бібліотеки Н, Е, бібліотеки бібліотеки J, M, Бібліотеки Бібліотеки K і бібліотек N . Це ускладнює повторне використання кожної бібліотеки незалежно від решти - наприклад, при тестуванні одиниць .

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

Дрібний графік

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

Однак, маючи на увазі, що для багатьох контейнерів DI вам не потрібно додавати жорсткі посилання на всі необхідні бібліотеки. Натомість ви можете використовувати пізнє зв’язування або у формі конвенції на основі конвенцій (сканування) (бажано) або конфігурації XML.

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

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


3
Дякую, зараз це має ідеальний сенс .. Мені потрібно було знати, чи це було за задумом. Щодо забезпечення правильного використання залежностей, я здійснив окремий проект зі своїм завантажувачем DI, таким як Стівен, згаданим нижче, де я посилаюся на решту бібліотек. Цей проект визначається програмою вхідної точки, і в кінці повної збірки це призводить до того, що всі необхідні dll знаходяться у папці bin. Дякую!
diegohb

2
@Mark Seemann Це питання / відповідь специфічні для Microsoft? Хотілося б знати, чи має сенс ця ідея переміщення всіх залежностей до "точки входу програми" для проекту Java EE / Spring з використанням Maven ... дякую!
Grégoire C

5
Ця відповідь стосується .NET. Ви можете звернутися до розділу " Принципи дизайну пакунків" Роберта К. Мартіна, наприклад, " Agile Development Software, Principles, Patterns and Practices"
Марк Семанн

7
@AndyDangerGagne Кореневий склад - це шаблон DI - протилежний Locator-сервісу . З точки зору складу Корінь, жоден із типів не є поліморфним; Кореневий склад розглядає всі типи як конкретні типи, і, отже, Принцип заміщення Ліскова не застосовується до нього.
Марк Семанн

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

65

Якщо я не використовував контейнер DI, мені не довелося б посилатися на бібліотеку EntityFramework у моєму додатку MVC3

Навіть використовуючи контейнер DI, вам не потрібно дозволяти посилатися на проект проекту MVC3 EF, але ви (неявно) вирішите це зробити, застосувавши Root Composition (шлях запуску, де ви складаєте свої об'єктивні графіки) всередині вашого проекту MVC3. Якщо ви дуже суворо захищаєте свої архітектурні межі за допомогою збірок, ви можете перенести логіку презентації на інший проект.

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

Витягування логіки презентації до бібліотеки класів може ускладнити речі при роботі з MVC. Це буде важче підключити все, оскільки контролери не є в проекті запуску (в той час як перегляди, зображення, файли css повинні, швидше за все, залишатися в проекті запуску). Це, мабуть, можливо, але для його налаштування знадобиться більше часу.

Через мінуси я, як правило, раджу просто зберігати корінь складу у веб-проекті. Багато розробників не хочуть, щоб їх збірка MVC залежала від складання DAL, але це насправді не проблема. Не забувайте, що збірки є артефактом розгортання ; Ви розділите код на кілька збірок, щоб дозволити розгортання коду окремо. З іншого боку, архітектурний шар - це логічний артефакт. Дуже цілком можливо (і звичайно) мати кілька шарів в одній збірці.

У цьому випадку ми матимемо корінь Composition (шар) та презентаційний шар у тому ж проекті веб-додатків (таким чином, в одній збірці). І незважаючи на те, що збірка посилань збірки , що містять DAL, Стрітення шар ще не посилається на Access Data Layer . Це велика відмінність.

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

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


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

1
Останній абзац чудовий і починає допомагати мені змінити свою думку щодо того, наскільки я суворий з дотриманням шарів в окремих збірках. Мати два або більше логічних шарів в одній збірці - це справді добре, якщо ви використовуєте інші процеси навколо написання коду (наприклад, огляди коду), щоб упевнитись, що у вашому коді користувальницького інтерфейсу немає посилань на класи DAL та наочно.
БенМ

6

Якщо я не використовував контейнер DI, мені не довелося б посилатися на бібліотеку EntityFramework у моєму додатку MVC3, лише на мій бізнес-рівень, який би посилався на мій рівень DAL / Repo.

Ви можете створити окремий проект під назвою "DependencyResolver". У цьому проекті вам потрібно посилатися на всі ваші бібліотеки.

Тепер шару інтерфейсу користувача не потрібна NHibernate / EF або будь-яка інша бібліотека, що не стосується інтерфейсу користувача, за винятком Castle Windsor.

Якщо ви хочете приховати Castle Windsor та DependencyResolver від свого шару інтерфейсу, ви можете написати HttpModule, який викликає дані реєстру IoC.

У мене є лише приклад для StructureMap:

public class DependencyRegistrarModule : IHttpModule
{
    private static bool _dependenciesRegistered;
    private static readonly object Lock = new object();

    public void Init(HttpApplication context)
    {
        context.BeginRequest += (sender, args) => EnsureDependenciesRegistered();
    }

    public void Dispose() { }

    private static void EnsureDependenciesRegistered()
    {
        if (!_dependenciesRegistered)
        {
            lock (Lock)
            {
                if (!_dependenciesRegistered)
                {
                    ObjectFactory.ResetDefaults();

                    // Register all you dependencies here
                    ObjectFactory.Initialize(x => x.AddRegistry(new DependencyRegistry()));

                    new InitiailizeDefaultFactories().Configure();
                    _dependenciesRegistered = true;
                }
            }
        }
    }
}

public class InitiailizeDefaultFactories
{
    public void Configure()
    {
        StructureMapControllerFactory.GetController = type => ObjectFactory.GetInstance(type);
          ...
    }
 }

DefaultControllerFactory не використовує контейнер IoC безпосередньо, але він делегує методам контейнерів IoC.

public class StructureMapControllerFactory : DefaultControllerFactory
{
    public static Func<Type, object> GetController = type =>
    {
        throw new  InvalidOperationException("The dependency callback for the StructureMapControllerFactory is not configured!");
    };

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType == null)
        {
            return base.GetControllerInstance(requestContext, controllerType);
        }
        return GetController(controllerType) as Controller;
    }
}

GetControllerДелегат встановлюється в StructureMap реєстрі (у Віндзорі він повинен бути Installer).


1
мені це подобається навіть краще, ніж те, що я закінчив робити, модулі чудові. тож, де я б зателефонував до Container.Dispose ()? Подія ApplicationEnd або EndRequest в модулі ...?
diegohb

1
@Steven Тому що Global.asax знаходиться у вашому шарі інтерфейсу MVC. HttpModule буде в проекті DependencyResolver.
Рокіян

1
Невелика користь полягає в тому, що ніхто не може використовувати контейнер IoC в інтерфейсі. Тобто ніхто не може використовувати контейнер IoC як сервіс-локатор в інтерфейсі користувача.
Rookian

1
Крім того, це забороняє розробників випадково використовувати DAL-код у шарі інтерфейсу, оскільки немає чітких посилань на збірку в інтерфейсі.
diegohb

1
Я зрозумів, як зробити те саме, використовуючи загальний API реєстрації Bootstrapper. Мій проект UI посилається на Bootstrapper, проект вирішення залежностей, де я підключаю свої реєстрації та проекти в моєму Core (для інтерфейсів), але нічого іншого, навіть мій DI Framework (SimpleInjector). Я використовую OutputTo nuget для копіювання dlls у папку bin.
diegohb

0
  • Існує залежність: якщо об'єкт інстанціює інший об’єкт.
  • Не існує залежності: якщо об'єкт очікує абстракцію (впорскування кондуктора, метод впорскування ...)
  • Посилання на складання (посилання на dll, веб-сервіси ..) не залежать від концепції залежності, оскільки для вирішення абстракції та вміння складати код, шар повинен посилатися на нього.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.