Please note that I have changed the code in the question.
Please see the server side code below (WCF Service):
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
namespace WcfService1
{
public class WindsorInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<IGreeting, Greeting>(),
Component.For<IGreetingService, GreetingService>());
}
}
public interface ILanguage
{
string SayHello();
}
public class Spanish : ILanguage
{
public string SayHello()
{
return "Hola";
}
}
public interface IGreeting
{
string SayHello();
}
public class Greeting: IGreeting
{
ILanguage Language;
public Greeting (ILanguage language)
{
Language = language;
}
public string SayHello()
{
return Language.SayHello();
}
}
public interface IGreetingFactory
{
IGreeting Create(ILanguage Language);
}
[ServiceContract]
public interface IGreetingService
{
[OperationContract]
string SayHello(string strLanguage);
}
public class GreetingService : IGreetingService
{
private readonly IGreetingFactory greetingFactory;
private IGreeting greeting;
public GreetingService()
{
}
public GreetingService(IGreetingFactory greetingFactory)
{
// store the factory until we need it
this.greetingFactory = greetingFactory;
}
public string SayHello (string strLanguage)
{
if (strLanguage == "S")
{
ILanguage Language = new Spanish();
Language = new Spanish();
greeting = new Greeting(Language);
}
return greeting.SayHello();
}
}
}
and the client side code below:
ServiceReference1.GreetingServiceClient s1 = new ServiceReference1.GreetingServiceClient();
string greeting = s1.SayHello("S");
ServiceReference1.GreetingServiceClient
is a service reference.
The code works as I would expect i.e. Castle Windsor allows me to inject a Greeting into the constructor of the service. However, the Greeting class itself has a parameterized constructor (it requires a Language). In the code above I have to initialise the Greeting (with a language) in the Say Hello
method of the service. How can I initialise the Greeting (with a language) in the constructor of the service?
One primary method* of providing run-time, user-driven, or otherwise dynamic dependencies is using factories to create your objects. Castle Windsor provides several different facilities to help with this, or you can use the kernel and implement a factory yourself.
Windsor's facility allows you to provide delegate-based factories, which are just methods for creating an object. You could use that here, but you lose some flexibility in what you can create (if you were to replace the implementation of ICalculator
to some other class, you'd have to update this method).
For maximum flexibility, you'll want to use Windsor's interface-based factories. With these, you provide a factory's interface and then Windsor will generate an implementation of it automatically.
Let's use a simplified version of your code above as an example. If you have just have this object:
public class Calculator : ICalculator
{
string Operator;
public Calculator(string operator)
{
Operator=operator;
}
}
And you wanted to pass operator
in when you create the object, you'd define a factory like so:
public interface ICalculatorFactory
{
ICalculator Create(string operator);
}
Then you'd register it in your installer:
kernel.Register(Component.For<ICalulatorFactory>().AsFactory());
Now, anywhere you wanted to use a calculator, you'd inject a factory for it, then just invoke Create
:
public class CalculatorUseExample
{
private readonly ICalculator addCalculator;
private readonly ICalculator subCalculator;
public CalculatorUseExample(ICalculatorFactory calculatorFactory)
{
addCalculator = calculatorFactory.Create("+");
subCalculator = calculatorFactory.Create("-");
}
}
Note that the name of the operator
parameter matters; by default (you can change this if you want), Windsor matches parameters by name.
If we add your CalculatorService
class back into the mix, you could use the same pattern:
public interface ICalculatorServiceFactory
{
ICalculatorService Create(string operator);
}
public class CalculatorService : ICalculatorService
{
private readonly ICalculator Calculator;
public CalculatorService(string operator, ICalculatorFactory calculatorFactory)
{
Calculator=calculatorFactory.Create(operator);
}
}
But I don't really like that because why should the service care what the operator is? That's a detail of the calculator. Instead, change the factory to just accept an ICalculator
and compose the objects together where you're creating this service:
public interface ICalculatorServiceFactory
{
ICalculatorService Create(ICalculator calculator);
}
public class CalculatorService : ICalculatorService
{
private readonly ICalculator Calculator;
public CalculatorService(ICalculator calculator)
{
Calculator=calculator;
}
}
public class CalculatorServiceUseExample
{
public CalculatorServiceUseExample(ICalculatorServiceFactory calculatorServiceFactory, ICalculatorFactory calculatorFactory)
{
var addCalculator = calculatorFactory.Create("+");
var service = calculatorServiceFactory.Create(addCalculator);
// TODO: use the service
}
}
There are advantages and disadvantages to use this pattern, which I go over in my answer here. Some advantages are that you can protect yourself from future changes and avoid service locator patterns. Disadvantages include a proliferation of interface objects and potentially viral usage of factories (see my first solution above where we had to create another factory).
* There are others of course, this is just the way I'd solve this particular situation because, to me, it indicates intent and is the most discoverable for readers of your code.
Based on your edit regarding WCF and what I understand you're trying to do, I'd implement the service contract like so:
public class CalculatorService : ICalculatorService
{
private readonly ICalculatorFactory calculatorFactory;
private ICalculator calculator;
public CalculatorService(ICalculatorFactory calculatorFactory)
{
// store the factory until we need it
this.calculatorFactory = calculatorFactory;
}
public void ChangeCalculatorServiceClient(string operator)
{
// A new operator, we'll need a new calculator
calculator = calculatorFactory.Create(operator);
}
}
Well, again you've changed your question to include another wrinkle; now you want to instantiate a different type based on a parameter. You can and should still use a factory for this, and this is how I'd go about it:
using Castle.Facilities.TypedFactory;
public class WindsorInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<IGreeting, Greeting>(),
Component.For<IGreetingFactory>().AsFactory(),
Component.For<IGreetingService, GreetingService>(),
Component.For<ILanguageFactory, LanguageFactory>());
}
}
public interface ILanguageFactory
{
ILanguage Create(string language);
}
public class LanguageFactory : ILanguageFactory
{
private readonly IKernel kernel;
public LanguageFactory(IKernel kernel)
{
this.kernel = kernel;
}
public ILanguage Create(string language)
{
switch (language)
{
case "S":
return kernel.Resolve<Spanish>();
default:
throw new ArgumentException();
}
}
}
public class GreetingService : IGreetingService
{
private readonly IGreetingFactory greetingFactory;
private readonly ILanguageFactory languageFactory;
private IGreeting greeting;
public GreetingService(IGreetingFactory greetingFactory, ILanguageFactory languageFactory)
{
// store the factory until we need it
this.greetingFactory = greetingFactory;
}
public string SayHello (string strLanguage)
{
var language = languageFactory.Create(strLanguage);
greeting = greetingFactory.Create(language);
return greeting.SayHello();
}
}