StructureMap and objects not setup for DI/IoC

为君一笑 提交于 2019-12-02 13:56:57

问题


I have a situation where I've created a factory method to create an object. However, the object has boilerplate code that needs execution before the object is created. Fixing that part of the design is out of scope for this question.

Also, when the object is created, a status display is updated on screen. This requires that this status display be instantiated before and be visible and the application be in a running state before creating this object. It is passed to the factory as a dependency.

I'm using v3.1.4.143 of StructureMap.

So, here's what I'd be doing in the normal world (pre-IoC):

GraphicsInterface GetGraphics()
{
    VideoDevicesList.GetVideoDevices();

    // Some logic here to determine the device to use...
    // Also, a status display is being updated to inform the user of 
    // what's happening at this point.
    VideoDevice device = ...;

    // The second parameter is a constant value, but the first is not.
    return new GraphicsInterface(device, featureLevels.FL5);
}

Seems simple enough, but ideally I'd like to be able to pass that graphics object around via injection as it'll be needed in many spots.

So, in structure map, I created a factory function to do the above. However it's giving me grief.

new Container(obj =>
      {
          // This is passed to the object that depends on it.
          // I've just left it out for brevity.
          // It'd go something like:  _graphics = _getGraphicsFactory();
          // where _getGraphicsFactory is the factory function below.
          For<Func<IStatusDisplay, GraphicsInterface>>
             .Use<Func<IStatusDisplay, GraphicsInterface>>(GetGraphics);
      }

Only this gives me an error about GraphicsInterface not being registered. That's fine, I should be able to register the GraphicsInterface object. Except that I can't register GraphicsInterface because the constructor requires two parameters, one of which must be queried before creating the object and can only be set up via the GetVideoDevices method above and it seems StructureMap tries to create the object for me when I call _getGraphicsFactory() (which is weird, I would have it expected it to execute my function to create the object).

I tried even calling GetInstance like this inside of my GetVideoDevices method:

_container
    .With<VideoDevice>(device)
    .With<FeatureLevel>(FeatureLevel.FL5)
    .GetInstance<Graphics>();

But no dice...

So, does anyone have an idea on how I'd get this to work?


回答1:


Whenever you are scratching your head trying to work out how to create instances at runtime, you need to step back and look for a design pattern that will fit the problem. DI is meant for composing applications, but controlling runtime behavior should be part of the application design - that is, the part that runs after the application is composed.

In this particular case, Abstract Factory would be a good fit. It allows you to separate the composed services (those injected through the constructor) from runtime services (those passed as method parameters).

However, you should restrict a factory to doing exactly one thing - creating the runtime instance. All other work should be part of other services. This gives you a clean way to inject a runtime object into your service and still allow the service behavior to be tested independently of this step.

public interface IGraphicsFactory
{
    GraphicsInterface Create(VideoDevice device);
    void Release(GraphicsInterface graphicsInterface);
}

public class GraphicsFactory : IGraphicsFactory
{
    private readonly FeatureLevel featureLevel;

    // Parameters injected are done so by the DI container
    public GraphicsFactory(FeatureLevel featureLevel)
    {
        this.featureLevel = featureLevel;
    }

    // Parameters passed are part of the application runtime state
    public GraphicsInterface Create(VideoDevice device)
    {
        return new GraphicsInterface(device, this.featureLevel);
    }

    // Method for releasing disposable dependencies (if any)
    public void Release(GraphicsInterface graphicsInterface)
    {
        var disposable = graphicsInterface as IDisposable;
        if (disposable != null)
            disposable.Dispose();
    }
}

Your factory can then be supplied to a service during application composition, and runtime instances of GraphicsInterface can be created at runtime. As per your requirement, this can easily be done in multiple spots by injecting it into the constructors of multiple services.

public class SomeService : ISomeService
{
    private readonly IGraphicsFactory graphicsFactory;

    public SomeService(IGraphicsFactory graphicsFactory)
    {
        if (graphicsFactory == null)
            throw new ArgumentNullException("graphicsFactory")

        this.graphicsFactory = graphicsFactory;
    }

    public void DoSomething()
    {
        // Get video device here. It will likely be best to 
        // delegate that to another specialized service
        // that is injected into this class.
        VideoDevice device = ...;

        var graphics = this.graphicsFactory.Create(device);
        try
        {
            // Do something with graphics
        }
        finally
        {
            this.graphicsFactory.Release(graphics);
        }
    }
}

As for selecting the device to use, that could either be done with another Abstract Factory, or if it is something that is done often, you could use a Strategy Pattern to load all of the options at composition time, and then selectively choose the device at runtime. Or, if your devices are disposable, you could make a Strategy of Abstract Factories or look to some more advanced design pattern to clean them up.

You might also consider using an adapter pattern to create an abstraction for GraphicsInterface if it doesn't already have a suitable that can be injected (and swapped) that has all of the members you are after.

public interface IGraphicsInterfaceAdapter
{
    // Extract all public properties of GraphicsInteface and define them here.
}

public class GraphicsInterfaceAdapter : IGraphicsInterfaceAdapter
{
    public GraphicsInterfaceAdapter(VideoDevice device, FeatureLevel featureLevel)
        : base(device, featureLevel)
    {
    }
}


来源:https://stackoverflow.com/questions/28703573/structuremap-and-objects-not-setup-for-di-ioc

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