Внедрение зависимостей и настройки приложений

Скажем, я определяю класс реализации браузера для своего приложения:

class InternetExplorerBrowser : IBrowser {
    private readonly string executablePath = @"C:\Program Files\...\...\ie.exe";

    ...code that uses executablePath
}

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

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

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

Я мог бы решить обе проблемы, передав executetablePath через конструктор:

class InternetExplorerBrowser : IBrowser {
    private readonly string executablePath;

    public InternetExplorerBrowser(string executablePath) {
        this.executablePath = executablePath;
    }
}

, но это вызовет проблемы в моей композиции Корень (метод запуска, который будет выполнять все необходимые классы разводки), так как тогда этот метод должен знать и как соединять вещи, и должен знать все эти маленькие данные настроек:

class CompositionRoot {
    public void Run() {
        ClassA classA = new ClassA();

        string ieSetting1 = "C:\asdapo\poka\poskdaposka.exe";
        string ieSetting2 = "IE_SETTING_ABC";
        string ieSetting3 = "lol.bmp";

        ClassB classB = new ClassB(ieSetting1);
        ClassC classC = new ClassC(B, ieSetting2, ieSetting3);

        ...
    }
}

, что легко превратит большой беспорядок ,

Я мог бы решить эту проблему, передав вместо этого интерфейс вида

interface IAppSettings {
    object GetData(string name);
}

всем классам, которые нуждаются в каких-то настройках. Затем я мог бы реализовать это либо как обычный класс со всеми встроенными в него настройками, либо как класс, который считывает данные из файла XML, что-то в этом роде. При этом следует ли иметь общий экземпляр класса AppSettings для всей системы или иметь класс AppSettings, связанный с каждым классом, который может понадобиться? Это, конечно, кажется излишним. Кроме того, наличие всех настроек приложения в одном месте позволяет легко увидеть и увидеть, какие могут быть все изменения, которые мне нужно сделать, когда я пытаюсь переместить программу на разные платформы.

Что может быть лучшим способом для этого? общая ситуация?

Редактировать:

А как насчет использования IAppSettings со всеми его настройками, жестко запрограммированными в нем?

interface IAppSettings {
    string IE_ExecutablePath { get; }
    int IE_Version { get; }
    ...
}

Это позволило бы обеспечить безопасность типов во время компиляции. Если бы я увидел, что интерфейс / конкретные классы слишком сильно растут, я мог бы создать другие меньшие интерфейсы вида IMyClassXAppSettings . Не будет ли это слишком тяжелым бременем для проектов среднего / среднего размера?

Я также читал об АОП и его преимуществах, связанных с сквозными проблемами (я думаю, что это один из них). Разве он не может предложить решения этой проблемы? Может быть, пометить переменные следующим образом:

class InternetExplorerBrowser : IBrowser {
    [AppSetting] string executablePath;
    [AppSetting] int ieVersion;

    ...code that uses executablePath
}

Затем, при компиляции проекта у нас также будет безопасность во время компиляции (имея проверку компилятором, что мы фактически реализовали код, который переплетается в данных. Это, конечно, привязало бы наш API к этому конкретный аспект.

11
задан devoured elysium 28 August 2010 в 18:45
поделиться

2 ответа

Отдельные классы должны быть максимально свободны от инфраструктуры - конструкции типа IApSettings, IMyClassXAppSettings, и [AppSetting] передают детали композиции в классы, которые в простейшем случае действительно зависят только от необработанных значений, таких как executablePath. Искусство внедрения зависимостей заключается в учете проблем.

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

Модули организуют приложения по подсистемам. Модуль предоставляет настраиваемые элементы подсистемы:

public class BrowserModule : Module
{
    private readonly string _executablePath;

    public BrowserModule(string executablePath)
    {
        _executablePath = executablePath;
    }

    public override void Load(ContainerBuilder builder)
    {
        builder
            .Register(c => new InternetExplorerBrowser(_executablePath))
            .As<IBrowser>()
            .InstancePerDependency();
    }
}

Это оставляет корень композиции с той же проблемой: он должен предоставить значение executablePath.Чтобы избежать конфигурационного супа, мы можем написать автономный модуль, который считывает параметры конфигурации и передает их в BrowserModule:

public class ConfiguredBrowserModule : Module
{
    public override void Load(ContainerBuilder builder)
    {
        var executablePath = ConfigurationManager.AppSettings["ExecutablePath"];

        builder.RegisterModule(new BrowserModule(executablePath));
    }
}

Вы можете рассмотреть возможность использования пользовательского раздела конфигурации вместо AppSettings. ; изменения будут локализованы в модуле:

public class BrowserSection : ConfigurationSection
{
    [ConfigurationProperty("executablePath")]
    public string ExecutablePath
    {
        get { return (string) this["executablePath"]; }
        set { this["executablePath"] = value; }
    }
}

public class ConfiguredBrowserModule : Module
{
    public override void Load(ContainerBuilder builder)
    {
        var section = (BrowserSection) ConfigurationManager.GetSection("myApp.browser");

        if(section == null)
        {
            section = new BrowserSection();
        }

        builder.RegisterModule(new BrowserModule(section.ExecutablePath));
    }
}

Это хороший шаблон, потому что каждая подсистема имеет независимую конфигурацию, которая считывается в одном месте. Единственное преимущество здесь — более очевидное намерение. Однако для нестроковых значений или сложных схем мы можем позволить System.Configuration сделать тяжелую работу.

15
ответ дан 3 December 2019 в 07:10
поделиться

Я бы выбрал последний вариант — передать объект, соответствующий интерфейсу IApSettings. На самом деле, я недавно выполнил этот рефакторинг на работе, чтобы разобраться с некоторыми модульными тестами, и он работал хорошо. Однако в этом проекте было несколько классов, зависящих от настроек.

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

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

Если это проблема для вас, вы можете обойти ее, используя инфраструктуру внедрения зависимостей, такую ​​как ninject (извините, если вы уже знакомы с такими проектами, как ninject — это может показаться немного снисходительным - если вы незнакомы, разделы зачем использовать ninject на github — хорошее место для изучения).

Используя ninject, для вашего основного проекта вы можете объявить, что хотите, чтобы любой класс с зависимостью от IApSettings использовал одноэлементный экземпляр вашего класса, основанного на AppSettings, без необходимости явно передавать это везде для конструкторов.

Затем вы можете настроить свою систему по-другому для своих модульных тестов, заявив, что вы хотите использовать экземпляр MockAppSettings везде, где используется IApSettings, или просто явно передав свой макет объекты напрямую.

Надеюсь, я правильно уловил суть вашего вопроса и помог вам — похоже, вы уже знаете, что делаете :)

2
ответ дан 3 December 2019 в 07:10
поделиться
Другие вопросы по тегам:

Похожие вопросы: