Dan
Dan

Reputation: 2165

Castle Windsor Abstract Factory

I'm trying to understand how to use TypedFactoryFacility to create an abstract factory, and I have it working at a basic level, however I don't fully understand how to scale it with runtime dependencies

Suppose I have a service that needs to be created at runtime:

public interface IRuntimeService {
  void DoThing();
}

with the following implementation

public class RuntimeService : IRuntimeService {
  public void DoThing() {
    // Do some work
  }
}

To create my IRuntimeService, I've created an abstract factory

public interface IRuntimeServiceFactory {
  IRuntimeService CreateService();
}

In my Castle installer, I'm using the TypedFactoryFacility to register my class and abstract factory.

public class TypeInstaller : IWindsorInstaller {

  public void Install(IWindsorContainer container, IConfigurationStore store) {
    container.AddFacility<TypedFactoryFacility>();
    container.Register(Component.For<IRuntimeService>().ImplementedBy<RuntimeService>());
    container.Register(Component.For<IRuntimeServiceFactory>().AsFactory());
  }

Then in my class that will be using the service, I can use the factory to create new service instances at runtime.

var myService = m_ServiceFactory.CreateService();

Everything above works perfectly, however I'm running into a problem when my RuntimeService class needs to be injected with a dependency chain itself that include runtime parameters.

To expand the example above, suppose I have a new runtime dependency

public interface IRuntimeDependency {
  void DoWork();
}

implemented by a class that takes a runtime string value through the constructor

public class RuntimeDependency : IRuntimeDependency {

  private readonly string m_Param;

  public RuntimeDependency(string param) {
    m_Param = param;
  }

  public void DoWork() {
    // Do work involving the param
  }
}

And the previously defined service class now needs a reference to the dependency

public class RuntimeService : IRuntimeService {

  private readonly IRuntimeDependency m_Dep;

  public RuntimeService(IRuntimeDependency dep) {
    m_Dep = dep;
  }

  public void DoThing() {
    // Do some work involving the dependency
    m_Dep.DoWork();
  }
}

How do I now I create instances of my service using the TypedFactoryFacility?

I would expect do just be able to change my factory method to look like

IRuntimeService CreateService(string param);

but Windsor throws an error 'Could not resolve non-optional dependency for parameter 'param' type 'System.String'.

Windsor knows how to create an IRuntimeDependency if I give it a string, and it knows how to create a IRuntimeService if I give it the dependency, so why can't it directly create a IRuntimeService with the string param?

I can make it work by having two distinct factory methods

IRuntimeService CreateService(IRuntimeDependency dep);
IRuntimeDependency CreateDependency(string param);

and creating the dependency, manually myself

var dep = m_ServiceFactory.CreateDependency(param);
var myService = m_ServiceFactory.CreateService(dep );

^^^This works, but the whole point of using a container is so that it will take care of assembling new objects for me. This is a relatively simple example involving only one dependency, but it would easily grow out of control with a more complex object graph.

I could of course create my own factory implementations, but that also nullifies the benefit of using the TypedFactoryFacility which is supposed to create the abstract factory implementations for you. I have a hard time believing there's not an existing solution to this problem but the Windsor examples don't contain any chained run-time dependencies.

I don't think using a FactoryComponentSelector is the correct approach because there's only one possible path to create the RuntimeService instance. It should be able to auto-resolve.

Upvotes: 0

Views: 900

Answers (3)

Jeff.B
Jeff.B

Reputation: 26

It is possible using DynamicParameters.

container.Register(Component.For<IRuntimeService>()
    .ImplementedBy<RuntimeService>()
    .LifestyleTransient()
    .DynamicParameters((k, d) => {
        d["dep"] = new RuntimeDependency((string)d["param"]);
    }));

Keep in mind that the dictionary keys have to match the parameter names in the CreateService method and RuntimeService constructor.

Edit: You should also make it LifestyleTransient if you intend to create a new instance each time the factory method is called. (The default is singleton)

Upvotes: 1

Dan
Dan

Reputation: 2165

It seems that what I am asking for is not possible by design.

See this other SO answer.

https://stackoverflow.com/a/3905496/2029835

Upvotes: 0

Scott Hannen
Scott Hannen

Reputation: 29302

In many or most cases, an object resolved by the container depends on implementations of other interfaces which are also resolved by the container. So as long as all of the interfaces have registered implementations, the container can resolve the entire dependency chain.

But in this case RuntimeDependency depends on a string, which isn't something the container can resolve.

public RuntimeDependency(string param) {
    m_Param = param;
}

In this case you can use the DependsOn method to explicitly provide a value to fulfill that dependency.

container.Register(Component.For<IRuntimeDependency, RuntimeDependency>()
    .DependsOn(Dependency.OnValue("param","whatEverTheValueIs")));

That value can, of course, come from configuration or wherever else. I use this a lot with SQL connection strings.

Upvotes: 2

Related Questions