How to use a factory with Dependecy Injection without resorting to using Service Locator pattern

不想你离开。 提交于 2019-12-09 05:09:06

问题


I have a GUI application. In it I allow a user to select from a container-provided list of algorithms. Each algorithm will be kicked off as a background task in another view. I need to support multiple instances of this view, and support multiple instances of the same algorithm. That view will also be provided by the container. The algorithm is also stateful.

So I have a case where I need to create instances of my view and algorithm and bind them together at runtime. I don't have static binding points for these instances, so I can't use the normal injection facilities (constructor or property injection). I don't want to call new, and I don't want to use the container like a Service Locator.

I solved this in Castle.Windsor with the Typed Factory Facility, but I had to deal with factories all over my app. The factory design was also a little strange because I had to return my instances to the factory when I was done with them.

I am now looking into using NInject because so far the learning curve and intro docs were much better, and I'd like to propose a container for my team to use. But for a scenario like this I think I'd have to write my own factories and call the kernel directly to resolve new instances (Service Locator embedded in a factory), as well as adding factory methods in my registration code.

Is there a generic way to solve this, or is this simply a problem that Dependency Injection wasn't designed to solve on its own?


Clarification:

I said in the comments that I'd like a specific answer for Ninject, and I've gotten that. And thanks very much :) In real life here I'll probably just use the pragmatic solutions that have been proposed.

But I botched my question by providing my basis as a concrete problem. I was hoping for a more purely fundamental answer to the question in my title.

Is there a pure-DI technique that allows the user to trigger new instances of components at runtime? Or would all such implementations use the container as a Service Locator, or require a specific "quirk" to the container (e.g. built-in factory support, ala Castle.Windsor or the soon-to-be-release Ninject factory feature), rather than utilize only aspects of "pure" DI?

I've only heard this word from the Java world, and I don't have much idea of what it means - so forgive me :) Is what I am looking for some kind of "outjection"?


回答1:


Best you create a factory interface like this

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

For Ninject 2.3 see https://github.com/ninject/ninject.extensions.factory and let it be implemented by Ninject by adding the following configuration.

Bind<IFooFactory>().AsFactory();

For 2.2 do the implementation yourself. This implementation is part of the container configuration and not part of your implementations.

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));
    }
}



回答2:


I really appreciate everyone else's answers. They're going to help me get this problem solved. I will most likely accept Remo's answer since it matches the current problem I actually face.

For my understanding I'd also like to get feedback on this broader answer I've come up with.


I was not sure Dependency Injection directly supported the mechanisms I'd talked about, through constructor, property, or method injection. These are what I'd consider "pure" DI at this point - though I'm willing to be swayed.

I thought injecting dependencies meant a relatively static object graph. It could be loaded from a config file, or procedurally generated, but it couldn't directly accommodate an unknowable runtime state, like a user repeatedly requesting new instances.

However after thinking through some of these alternatives I'm starting to think that there are work-arounds that support the purity, and that maybe the purity I described isn't as important as I'd thought. Some of the less "pure" options still work with most containers in a mostly clean manner, and it seems easy enough to add support to the container to clean them up the rest of the way.

Here are the work-arounds I've considered so far (a few of these were already mentioned).

Reference the container in custom factories, then wash your hands:

Your components can be implemented however you want. You can use whatever container you want (as long as it supports transient instances). You just have to live with the fact that you'll be injecting factories into your code, and that those factories will resolve from the container directly. Who needs purity when you can be pragmatic?

Example code:

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);
    }
}

Use a container-specific factory extension:

Your components can be implemented however you want. But your container must directly support injecting factories into your code, and auto-implementing those factories so they don't have to have any specific knowledge of the container.

Example code:

// 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);
}

Use a container-specific object pool:

Make sure your components are stateless, and allow them to be pooled. The container will take care of allocating or pooling objects for you. This is more or less a managed factory with a fancy implementation, that you have to both allocate from and deallocate to.

Pseudo-code (I haven't used a conatiner-based object pool before):

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);
    }
}

The advantage of this pseudo-code is that you didn't have to define the interface for your factory. The disadvantage is that you had to depend on the pool interface, so your container has its tendrils on you. Also I didn't get to pass in anything to the Get method. This probably makes sense because the objects have to support instance reuse.

If real pools don't work like this, they might look identical to the "container-specific factory extension" example above, only they'd always require a Release method along with the Create method.

Use the Flyweight Pattern:

(Flyweight Pattern - Not sure if I've identified the pattern correctly, or if I just have a weird use for it)

Inject a stateless component that acts as the behavior for your objects, or the "heavy-weight" component. Support separate "instances" of the component using flyweight state objects that you pass to the behavior component, or have them wrap the behavior component.

This will drastically effect the architecture of your components. Your implementations must be stateless, and your state object must be designed in a way that it will work for all possible component implementations. But it completely supports the "pure" injection model (only injection into constructors, properties, methods).

This won't work well for UIs though. View classes usually need to be created directly, and we don't want our "flyweight" to be a UI class...

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);
    }
}

Use child containers for each new instance the user requests:

The container works best when creating an object graph from a single root. Instead of trying to fight this, work with it. When the user clicks a button to create a new instance of your algorithm, create a new child container for those objects and call into global configuration code however it needs to be done. Then attach the child container to the parent container.

This means the spawning code will need to know about the container at some level. Maybe wrapping it in a factory is the best way.

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);
    }
}

Kind of looks like where we started. But we have a new object graph root, so I'm not sure I can call this out for using the Service Locator pattern. The problem with this approach is the most tightly coupled to the container. Not only is the container directly referenced, but the factory relies on the implementation detail of the container supporting child containers.




回答3:


I always like to show general answers, without going to much into features of special containers. You say the following:

  1. You need consumers to ask for new instances.
  2. Instances must be returned (probably to be reused).

Using a factory is probably the best approach. The second requirement can be solved by returning IDisposable objects. You can then write code like this:

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

There are multiple ways of doing this, but you can let the algorithm interface implement IDisposable:

public interface IAlgorithm : IDisposable
{
}

Instead of returning the real algorithm, you can return an decorator, that allows that instance to be returned to the pool:

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;
        }
    }
}

Now your factory can wrap the real algorithms with the decorator and return that to the consumer:

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);
    }
}

This is of course just an example. Your design will probably be different. Depending on the container you choose, you might get support for factories, object pooling and what not. I think it is important to find the correct design first and the next step is to see whether your container might be able to help you with this.



来源:https://stackoverflow.com/questions/8166696/how-to-use-a-factory-with-dependecy-injection-without-resorting-to-using-service

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!