Зависимость вводит (DI) “дружественная” библиотека

Я обдумываю дизайн библиотеки C#, которая будет иметь несколько различных функций высокого уровня. Конечно, те высокоуровневые функции будут реализованы с помощью ТВЕРДЫХ принципов разработки класса как можно больше. По сути, вероятно, будут классы, предназначенные, чтобы потребители использовали непосредственно регулярно, и "классы поддержки", которые являются зависимостями тех более общих классов "конечного пользователя".

Вопрос, что является лучшим способом разработать библиотеку, таким образом, это:

  • Агностик DI - Хотя добавляя основную "поддержку" один или две из общих библиотек DI (StructureMap, Ninject, и т.д.) кажется разумным, я хочу, чтобы потребители были в состоянии пользоваться библиотекой с любой платформой DI.
  • Применимый неDI - Если потребитель библиотеки не использует DI, библиотека, должен все еще быть максимально простым в использовании, уменьшив объем работы, который пользователь должен сделать для создания всех этих "неважных" зависимостей только для получения до "реальных" классов, которые они хотят использовать.

Мои существующие взгляды состоят в том, чтобы предоставить несколько "регистрационных модулей DI" общим библиотекам DI (например, реестр StructureMap, модуль Ninject), и набор или классы Фабрики, которые являются неDI и содержат связь с теми немногими фабриками.

Мысли?

227
задан Pete 11 January 2010 в 14:20
поделиться

4 ответа

Еще одно не очень хорошее решение :

Можно поместить файлы конфигурации в различные «необязательные группы» и загрузить определенную конфигурацию вручную с помощью StartDeployment.DownloadFileGroup . Но, что касается меня, это не идеальное решение.

-121--4028839-

Вы также можете просто искать на таких сайтах, как Tucows и CNET, они также имеют его там.

-121--1119160-

Это на самом деле просто сделать, как только вы понимаете, что DI о узоры и принципы , а не технологии.

Чтобы разработать API в DI Container-agnostic способом, следуйте этим общим принципам:

Program to a interface, а не реализация

Этот принцип на самом деле является цитатой (из памяти) из Design Patterns , но это всегда должна быть ваша реальная цель . DI является всего лишь средством достижения этой цели .

Применяйте Принцип Голливуда

Принцип Голливуда в терминах DI говорит: Не называйте Контейнер DI, он позвонит вам .

Никогда напрямую не запрашивайте зависимость, вызывая контейнер из вашего кода. Попросите об этом неявно с помощью Впрыск конструктора .

Use Constructor Injection

Если вам нужна зависимость, попросите ее статически через конструктор:

public class Service : IService
{
    private readonly ISomeDependency dep;

    public Service(ISomeDependency dep)
    {
        if (dep == null)
        {
            throw new ArgumentNullException("dep");
        }

        this.dep = dep;
    }

    public ISomeDependency Dependency
    {
        get { return this.dep; }
    }
}

Обратите внимание, как класс Service гарантирует свои инварианты. После создания экземпляра гарантируется доступность зависимости из-за комбинации предложения Guard и ключевого слова readonly .

Используйте абстрактную фабрику, если вам нужен недолговечный объект

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

Для получения дополнительной информации см. this .

Составьте только в последний ответственный момент

Сохраняйте объекты разъединенными до самого конца. Как правило, можно подождать и подключить все данные в точке входа приложения. Это называется Корень композиции .

Подробнее здесь:

Упростить с помощью фасада

Если вы считаете, что полученный API становится слишком сложным для начинающих пользователей, вы всегда можете предоставить несколько фасад

Чтобы обеспечить гибкий фасад с высокой степенью обнаруживаемости, вы могли бы рассмотреть возможность предоставления Fluent Builders. Что-то вроде этого:

public class MyFacade
{
    private IMyDependency dep;

    public MyFacade()
    {
        this.dep = new DefaultDependency();
    }

    public MyFacade WithDependency(IMyDependency dependency)
    {
        this.dep = dependency;
        return this;
    }

    public Foo CreateFoo()
    {
        return new Foo(this.dep);
    }
}

Это позволило бы пользователю создать Foo по умолчанию, написав

var foo = new MyFacade().CreateFoo();

Однако, было бы очень понятно, что можно предоставить пользовательскую зависимость, и вы могли бы написать

var foo = new MyFacade().WithDependency(new CustomDependency()).CreateFoo();

Если вы представите, что класс MyFacade инкапсулирует много различных зависимостей, я надеюсь, что будет ясно, как он будет обеспечивать правильные значения по умолчанию, все еще делая расширяемость discapsade


FWIW, задолго после написания этого ответа, я расширил концепции здесь и написал более длинный блог о DI-Friendly Libraries , и сопутствующий пост о DI-Friendly Рамки .

357
ответ дан 23 November 2019 в 03:51
поделиться

Это должно сделать трюк

UPDATE
    <tablename> 
SET
    <fieldname> = replace(<fieldname>,'\n',char(13)+char(10)),
    <otherfieldname> = replace(< otherfieldname >,'\n',char(13)+char(10))
-121--3866360-

Я считаю, что вы ищете char (13)...

select 'hi' + char(13) + 'there'

Можно заменить\n в инструкциях select...

select replace('hi\nthere', '\n', char(13))

Или вы можете сделать обновление...

update table set str = replace(str, '\n', char(13))
-121--3866362-

Термин «инъекция зависимостей» не имеет никакого отношения к контейнеру IoC вообще, даже если вы склонны видеть их вместе. Это просто означает, что вместо написания кода:

public class Service
{
    public Service()
    {
    }

    public void DoSomething()
    {
        SqlConnection connection = new SqlConnection("some connection string");
        WindowsIdentity identity = WindowsIdentity.GetCurrent();
        // Do something with connection and identity variables
    }
}

Вы пишете это так:

public class Service
{
    public Service(IDbConnection connection, IIdentity identity)
    {
        this.Connection = connection;
        this.Identity = identity;
    }

    public void DoSomething()
    {
        // Do something with Connection and Identity properties
    }

    protected IDbConnection Connection { get; private set; }
    protected IIdentity Identity { get; private set; }
}

То есть вы делаете две вещи, когда пишете свой код:

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

  2. Вместо создания экземпляров этих интерфейсов внутри класса передайте их в качестве аргументов конструктора (в качестве альтернативы они могут быть назначены общим свойствам; первый - инъекция конструктора , второй - инъекция свойства ).

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

Если вы ищете пример, посмотрите не далее самой Рамки .NET:

  • Список < T > реализует IList < T > . При проектировании класса для использования IList < T > (или IEnumerable < T > ) можно воспользоваться такими понятиями, как «ленивая загрузка», как Linq to SQL, Linq to Сущностей и NHibernate, как, например, закулисная загрузка свойств. Некоторые классы рамки фактически принимают IList < T > в качестве аргумента конструктора, например BindingList < T > , который используется для нескольких функций привязки данных.

  • Linq к SQL и EF построены полностью вокруг IDbConnection и связанных интерфейсов, которые могут передаваться через общедоступные конструкторы. Но вам не нужно их использовать; конструкторы по умолчанию работают нормально с последовательностью соединения, находящейся где-то в файле конфигурации.

  • Если вы когда-либо работаете с компонентами WinForms, вы имеете дело с «сервисами», такими как INameCreationService или IExtenderProviveService . Вы даже не знаете, что представляют собой конкретные классы . .NET на самом деле имеет свой собственный контейнер IoC, IContainer , который используется для этого, и класс Component имеет метод GetService , который является фактическим локатором службы. Конечно, ничто не мешает использовать любой или все эти интерфейсы без IContainer или конкретного локатора. Сами услуги лишь свободно связаны с контейнером.

  • Контракты в WCF строятся полностью вокруг интерфейсов. На фактический конкретный класс обслуживания обычно ссылаются по имени в файле конфигурации, который по существу является DI. Многие люди не понимают этого, но полностью возможно заменить эту систему конфигурации на другой контейнер IoC. Возможно, более интересно,поведение службы - это все экземпляры IServiceBehavior , которые можно добавить позже. Опять же, вы можете легко провести его в контейнер IoC и выбрать соответствующие варианты поведения, но функция полностью пригодна без него.

И так далее и так далее. Вы найдете DI по всему месту в .NET, просто обычно это делается так легко, что вы даже не думаете об этом как DI.

Если вы хотите спроектировать библиотеку с поддержкой DI для максимального удобства использования, лучше всего предоставить собственную реализацию IoC по умолчанию с использованием облегченного контейнера. IContainer - отличный выбор, потому что он является частью самой Рамки .NET.

39
ответ дан 23 November 2019 в 03:51
поделиться

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

Затем разоблачить слой над логикой DI для пользователей библиотеки, чтобы они могли использовать любые рамки, которые вы выбрали через ваш интерфейс. Таким образом, они все еще могут использовать функциональность DI, которые вы выставлены, и они могут свободно использовать любые другие рамки для своих целей.

Позволяет пользователям библиотеки подключить собственную Di Framework, кажется мне немного неправильно, так как это резко увеличивает объем обслуживания. Это также становится больше среды плагинов, чем прямой.

1
ответ дан 23 November 2019 в 03:51
поделиться

Edit 2015 : время прошло, я понимаю, что все это было огромной ошибкой. Контейнеры IOC ужасны, и ди - очень плохой способ справиться с побочными эффектами. Эффективно, все ответы здесь (и сам вопрос) следует избегать. Просто будьте в курсе побочных эффектов, отделите их из чистого кода, и все остальное происходит на месте или не имеет значения и ненужная сложность.

Оригинальный ответ следует:


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

Я закончил написать свой собственный очень простой встроенный контейнер IOC , а также обеспечивающий Windsor Facility и модуль Ninject . Интеграция библиотеки с другими контейнерами - это просто вопрос правильной проводки компонентов, поэтому я мог легко интегрировать его с Autofac, Unity, StructureMap, что угодно.

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

Подробнее в этом пост блога .

Masstransit , кажется, полагается на что-то подобное. Он имеет интерфейс Интерфейс IObjectbuder Ibject, который действительно является IserviceLocator CommonserviCelocator с парой больше методов, то он реализует это для каждого контейнера, то есть NinjectObjectbuilder и регулярный модуль / объект, то есть masstransitmodule . Затем полагается на iObjectbuilder , чтобы создать то, что ему нужно. Это действительный подход, конечно, но лично мне это не очень нравится, так как он на самом деле слишком много проходит вокруг контейнера, используя его в качестве сервисного локатора.

Монорелью внедряет свой собственный контейнер , который реализует старый старый ISERVICEPROVIDER . Этот контейнер используется на протяжении всей этой рамки через интерфейс , который предоставляет известные услуги . Чтобы получить бетонный контейнер, он имеет встроенный локатор поставщика услуг . Виндзор Windsor Parit указывает на этот локатор поставщика услуг для Windsor, что делает его выбранным поставщиком услуг.

Нижняя строка: идеального решения нет. Как и в случае любого решения, этот вопрос требует баланса между гибкостью, ремонтопригодностью и удобством.

4
ответ дан 23 November 2019 в 03:51
поделиться
Другие вопросы по тегам:

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