навколишній контекст проти введення конструктора


9

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

У мене є дві можливі реалізації. Основний клас, який приймає IAmbientContext з усіма трьома класами або вводить для всіх класів три класи.

public interface ISessionContext 
{
    ...
}

public class MySessionContext: ISessionContext 
{
    ...
}

public interface ILogManager 
{

}

public class MyLogManager: ILogManager 
{
    ...
}

public interface IService 
{
    ...
}

public class MyService: IService
{
    ...
}

Перше рішення:

public class AmbientContext
{
    private ISessionContext sessionContext;
    private ILogManager logManager;
    private IService service;

    public AmbientContext(ISessionContext sessionContext, ILogManager logManager, IService service)
    {
        this.sessionContext = sessionContext;
        this.logManager = logManager;
        this.service = service;
    }
}


public class MyCoreClass(AmbientContext ambientContext)
{
    ...
}

друге рішення (без навколишнього контексту)

public MyCoreClass(ISessionContext sessionContext, ILogManager logManager, IService service)
{
    ...
}

Яке найкраще рішення в цьому випадку?


Що " IServiceвикористовується для спілкування з іншими службами?" Якщо IServiceце нечітка залежність від інших служб, то це звучить як локатор сервісу і не має існувати. Ваш клас повинен залежати від інтерфейсів, які чітко описують, що з ними буде робити їх споживач. Жоден клас ніколи не потребує послуги, щоб забезпечити доступ до послуги. Класу потрібна залежність, яка робить щось конкретне, що потрібно класу.
Скотт Ханнен

Відповіді:


4

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

Якщо ви створюєте AmbientContextта вводите це в багато класів, ви потенційно надаєте кожному з них більше інформації, ніж їм потрібно (наприклад, клас Fooможе використовуватись лише ISessionContext, але про них розповідають про ILogManagerі ISessionтеж).

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

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


4

Термінологія запитання насправді не відповідає прикладу коду. Це Ambient Contextшаблон, що використовується для того, щоб якомога простіше перехопити залежність від будь-якого класу в будь-якому модулі, не забруднюючи кожен клас приймати інтерфейс залежності, але все ж зберігаючи ідею інверсії управління. Такі залежності, як правило, присвячені реєстрації, безпеці, керуванню сеансом, транзакціям, кешування, аудиту, таким чином, для будь-якої наскрізної проблеми в цій програмі. Це як - то дратує додати ILogging, ISecurity, ITimeProviderконструкторам і більшу частину часу не всі класи потрібно в одне і теж час, тому я розумію вашу потребу.

Що робити, якщо термін служби ISessionекземпляра відрізняється від тривалості ILogger? Можливо, екземпляр ISession повинен бути створений на кожен запит та ILogger один раз. Тому наявність усіх цих залежностей, якими керує один об'єкт, який не є самим контейнером, не виглядає правильним вибором через всі ці проблеми з управлінням життям та локалізацією та інші, описані в цій темі.

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

Таким чином, найпростіший спосіб НЕ використовувати конструкторський нагнітання чи будь-який інший механізм впорскування для боротьби з наскрізними залежностями, а використовуючи статичний виклик . Насправді ми часто бачимо цю закономірність, реалізовану самими рамками. Перевірте Thread.CurrentPrincipal, що є статичним властивістю, яке повертає реалізацію IPrincipalінтерфейсу. Він також встановлений, щоб ви могли змінити реалізацію, якщо вам це подобається, таким чином, ви не зв'язані з нею.

MyCore виглядає зараз щось на кшталт

public class MyCoreClass
{
    public void BusinessFeature(string data)
    {
        LoggerContext.Current.Log(data);

        _repository.SaveProcessedData();

        SessionContext.Current.SetData(data);
        ...etc
    }
}

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

Ви хочете , щоб уникнути AmbientContext.Current.Logger, AmbientContext.Current.Sessionз тих же причин , як описано вище.

Але у вас є інші варіанти вирішення цієї проблеми: використовуйте декоратори, динамічне перехоплення, якщо ваш контейнер має таку можливість або AOP. Контекст навколишнього середовища повинен бути останнім заходом через те, що його клієнти приховують через нього свої залежності. Я б все-таки використовував Ambient Context, якщо інтерфейс він насправді імітує мій імпульс використовувати статичну залежність на зразок DateTime.Nowабо, ConfigurationManager.AppSettingsі ця потреба виникає досить часто. Зрештою, введення конструктора може бути не такою поганою ідеєю, щоб отримати ці всюдисущі залежності.


3

Я б уникнув цього AmbientContext.

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

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

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

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

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


2

Другий (без інтерфейсної обгортки)

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

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