Как использовать фабрику с Dependecy Injection, не прибегая к использованию шаблона Service Locator - PullRequest
3 голосов
/ 17 ноября 2011

У меня есть приложение с графическим интерфейсом.В нем я разрешаю пользователю выбирать из предоставленного контейнером списка алгоритмов.Каждый алгоритм будет запущен в качестве фоновой задачи в другом представлении.Мне нужно поддерживать несколько экземпляров этого представления и поддерживать несколько экземпляров одного и того же алгоритма.Это представление также будет предоставлено контейнером.Алгоритм также с состоянием.

Так что у меня есть случай, когда мне нужно создать экземпляры моего представления и алгоритма и связать их вместе во время выполнения.У меня нет статических точек привязки для этих экземпляров, поэтому я не могу использовать обычные средства инъекции (конструктор или инъекция свойства).Я не хочу вызывать new и не хочу использовать контейнер как Сервисный локатор .

. Я решил это в Castle.Windsor с помощью Типизированный Factory Facility , но мне приходилось иметь дело с фабриками по всему приложению.Заводской дизайн также был немного странным, потому что мне пришлось возвращать свои экземпляры на завод, когда я закончил с ними.

Сейчас я изучаю использование NInject, потому что до сих пор кривая обучения и вводные документы были намного лучше, и я хотел бы предложить контейнер для моей команды для использования.Но для такого сценария я думаю, что мне придется написать свои собственные фабрики и напрямую вызвать ядро ​​для разрешения новых экземпляров (Service Locator, встроенный в фабрику), а также добавить фабричные методы в мой регистрационный код.

Существует ли общий способ решения этой проблемы или это просто проблема, для решения которой Dependency Injection не была разработана самостоятельно?


Пояснение:

Я сказал в комментариях, что хотел бы получить конкретный ответ для Ninject, и я его получил.И большое спасибо :) В реальной жизни здесь я, вероятно, просто буду использовать предложенные прагматические решения.

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

Существует ли метод чистого DI, позволяющий пользователю запускать новые экземпляры компонентов во время выполнения?Или все такие реализации будут использовать контейнер в качестве локатора службы или потребуют особой «причуды» для контейнера (например, встроенная поддержка фабрики, ala Castle.Windsor или функция фабрики Ninject, которая скоро будет выпущена), скореечем использовать только аспекты "чистого" DI?

Я только слышал это слово из мира Java, и я не очень понимаю, что это значит - так что прости меня :) Это то, что я естьищет какую-то «изгнанницу»?

Ответы [ 3 ]

8 голосов
/ 17 ноября 2011

Лучше всего, если вы создадите заводской интерфейс, подобный этому

public interface IFooFactory
{
    IFoo CreateFoo(int someParameter);
}

Для Ninject 2.3 см. https://github.com/ninject/ninject.extensions.factory, и пусть он будет реализован Ninject путем добавления следующей конфигурации.

Bind<IFooFactory>().AsFactory();

Для 2.2 сделать реализацию самостоятельно.Эта реализация является частью конфигурации контейнера, а не частью ваших реализаций.

public class FooFactory: IFooFactory
{
    private IKernel kernel;
    public FooFactory(IKernel kernel)
    {
        this.kernel = kernel;
    }

    public ISession CreateFoo(int someParameter)
    {
        return this.kernel.Get<IFoo>(
            new ConstructorArgument("someParameter", someParameter));
    }
}
3 голосов
/ 18 ноября 2011

Я действительно ценю ответы всех остальных. Они помогут мне решить эту проблему. Скорее всего, я приму ответ Ремо, поскольку он соответствует текущей проблеме, с которой я на самом деле сталкиваюсь.

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


Я не был уверен, что Dependency Injection напрямую поддерживает механизмы, о которых я говорил, через конструктор, свойство или внедрение метода. Это то, что я бы назвал «чистым» DI на данный момент - хотя я готов к тому, чтобы его покачали.

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

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

Вот обходные пути, которые я рассмотрел до сих пор (некоторые из них уже упоминались).

Ссылка на контейнер на пользовательских фабриках, а затем мыть руки:

Ваши компоненты могут быть реализованы так, как вы хотите. Вы можете использовать любой контейнер, который хотите (если он поддерживает временные экземпляры). Вам просто нужно смириться с тем фактом, что вы будете внедрять фабрики в ваш код, , и что эти фабрики будут напрямую обрабатываться из контейнера . Кому нужна чистота, когда вы можете быть прагматичным?

Пример кода:

public class ComponentFactory // Might inherit from an interface...
{
    private readonly IContainer container;

    public ComponentFactory(IContainer container)
    {
        this.container = container;
    }

    public IComponent Create(IOtherComponent otherComponent)
    {
        return container.Get<IComponent>(otherComponent);
    }
}

Используйте фабричное расширение для конкретного контейнера:

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

Пример кода:

// Black magic - the container implemented it for us!
// But the container basically implemented our code from the previous example...
public interface IComponentFactory
{
    public IComponent Create(IOtherComponent otherComponent);
}

Использовать пул объектов для конкретного контейнера:

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

Псевдокод (ранее я не использовал пул объектов на основе conatiner):

public class SomeUI
{
    private readonly IComponentPool componentPool;

    public OtherComponent(IComponentPool componentPool)
    {
        this.componentPool = componentPool;
    }

    public void DoSomethingWhenButtonPushed()
    {
        var component = componentPool.Get();
        component.DoSomething();
        componentPool.Release(component);
    }
}

Преимущество этого псевдокода в том, что вам не нужно было определять интерфейс для вашей фабрики. Недостатком является то, что вы должны зависеть от интерфейса пула, поэтому у вашего контейнера есть свои усики. Кроме того, я не смог ничего передать методу Get. Это, вероятно, имеет смысл, поскольку объекты должны поддерживать повторное использование экземпляра.

Если реальные пулы не работают таким образом, они могут выглядеть идентично приведенному выше примеру «фабричного расширения контейнера», только для них всегда требуется метод Release вместе с методом Create.

Использовать шаблон Flyweight:

( Шаблон Flyweight - Не уверен, правильно ли я определила модель, или просто странно ее использую)

Внедрите компонент без состояния, который действует как поведение для ваших объектов, или компонент «большого веса». Поддержка отдельных «экземпляров» компонента с использованием объектов состояния flyweight, которые вы передаете компоненту поведения, или заставьте их обернуть компонент поведения.

Это сильно повлияет на архитектуру ваших компонентов. Ваши реализации должны быть без состояний, а ваш объект состояния должен быть спроектирован так, чтобы он работал для всех возможных реализаций компонентов. Но он полностью поддерживает «чистую» модель внедрения (только внедрение в конструкторы, свойства, методы).

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

public class ComponentState
{
    // Hopefully can be less generic than this...
    public Dictionary<string, object> Data { get; set; }
}

public interface IComponent
{
    int DoSomething(ComponentState state);
}

public SomeUI
{
    private readonly IComponent component;

    public OtherComponent(IComponent component)
    {
        this.component = component;
    }

    public void DoSomethingWhenButtonPushed()
    {
        var state = new ComponentState();
        component.DoSomething(state);
    }
}

Использовать дочерние контейнеры для каждого нового экземпляра, который запрашивает пользователь:

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

Это означает, что код вызова должен знать о контейнере на некотором уровне.Может быть, лучше всего обернуть его на заводе.

public class SubComponentFactory // Might inherit from an interface...
{
    private readonly IContainer container;

    public ComponentFactory(IContainer container)
    {
        this.container = container;
    }

    public IComponent Create(IOtherComponent otherComponent)
    {
        // Todo: Figure out any lifecycle issues with this.
        // I assume the child containers get disposed with the parent container...

        var childContainer = container.CreateChildContainer();
        childContainer.Configure(new SubComponentConfiguration());

        return childContainer.Get<SubComponent>(otherComponent);
    }
}

Вид выглядит так, как мы начали.Но у нас есть новый корневой объектный граф, поэтому я не уверен, что смогу это использовать для использования шаблона Service Locator.Проблема с этим подходом наиболее тесно связана с контейнером.Мало того, что на контейнер ссылаются напрямую, фабрика опирается на детали реализации контейнера, поддерживающего дочерние контейнеры.

2 голосов
/ 17 ноября 2011

Мне всегда нравится показывать общие ответы, не вдаваясь в детали специальных контейнеров. Вы говорите следующее:

  1. Вам нужно, чтобы потребители запрашивали новые экземпляры.
  2. Экземпляры должны быть возвращены (возможно, для повторного использования).

Использование фабрики, вероятно, лучший подход. Второе требование может быть решено путем возврата IDisposable объектов. Затем вы можете написать код так:

using (var algorithm = this.algoFactory.CreateNew(type))
{
    // use algorithm.
}

Есть несколько способов сделать это, но вы можете позволить интерфейсу алгоритма реализовать IDisposable:

public interface IAlgorithm : IDisposable
{
}

Вместо возврата реального алгоритма вы можете вернуть декоратор, который позволяет вернуть этот экземпляр в пул:

public sealed class PooledAlgorithmDecorator : IAlgorithm
{
    private readonly IAlgorithmPool pool;
    private IAlgorithm real;

    public PooledAlgorithmDecorator(IAlgorithm real,
        IAlgorithmPool pool)
    {
        this.real = real;
        this.pool = pool;
    }

    public void Dispose()
    {
        if (this.real != null)
        {
            this.Pool.ReturnToPool(this.real);
            this.real = null;
        }
    }
}

Теперь ваша фабрика может обернуть реальные алгоритмы декоратором и вернуть их потребителю:

public class PooledAlgorithmFactory : IAlgorithmFactory
{
    private readonly IAlgorithmPool pool;

    public PooledAlgorithmFactory(IAlgorithmPool pool)
    {
        this.pool = pool;
    }

    public IAlgorithm CreateNew(string type)
    {
        var instance = this.pool.GetInstanceOrCreateNew(type);

        return new PooledAlgorithmDecorator(instance, this.pool);
    }
}

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

...