Castle Windsor
Castle Windsor is an inversion of control container. It's built on top of a MicroKernel responsible for inspecting the classes and trying to understand what they need in order to work and then provide the requirements or fail fast if something is wrong.
For our small example, the following code will take care of everything:
IWindsorContainer container = new WindsorContainer();
container.AddComponent( "newsletter", typeof(INewsletterService),
typeof(SimpleNewsletterService) );
container.AddComponent( "smtpemailsender", typeof(IEmailSender),
typeof(SmtpEmailSender) );
container.AddComponent( "templateengine", typeof(ITemplateEngine),
typeof(NVelocityTemplateEngine) );
// Ok, Lets start Now...
INewsletterService service = (INewsletterService) container["newsletter"];
service.Dispatch("hammett at gmail dot com", friendsList, "merryxmas");
First, let me explain what happened in this code snippet:
1. You registered the service INewsletterService on the container and you also said that the class SimpleNewsletterService contains an implementation of INewsletterService. The first argument is a key that you use to request this service later. You might also request the service by the Type.
2. Windsor inspected the implementation you provided and noticed that it requires two other services to work. It checked itself and realized that it doesn't have such services registered, so if somebody requests the INewsletterService service instance, it will throw an exception.
3. You registered the service IEmailSender. Windsor notices that the implementation has just one public constructor that receives two arguments (host and port). It's not able to satisfy this configuration as we haven't provided an external configuration. We'll fix that later.
4. You registered the service ITemplateEngine. Windsor registers it successfully and shouts to other components in a WaitingDependency state that a component has been registered. The INewsletterService realizes that one of its dependencies is OK to go, but it still is waiting for the other.
The fact that a class has a non default constructor has a specific meaning for the container. The constructor says to it: "Look, I really need these in order to work". If our implementation were different, the container would have used a different approach. For example:
public class SmtpEmailSender : IEmailSender
{
private String _host;
private int _port;
public SmtpEmailSender()
{
_host = "mydefaulthost";
_port = 110; // default port
}
public String Host
{
get { return _host; }
set { _host = value; }
}
public int Port
{
get { return _port; }
set { _port = value; }
}
...
}
In this case, you may or may not specify the host and port in an external configuration and the container will be able to use it. Another approach is to expose more than one constructor. The container will try to use the best constructor - meaning the one it can satisfy more arguments.
"But wait! What about the configuration?"
By default, Windsor will try to obtain the component configuration from the XML file associated with the AppDomain. However, you can override it by implementing the interface IConfigurationStore. There's another implementation of IConfigurationStore that reads the configuration from standard XML files, which is good when you have a configuration for testing environment, another for production, and so on.
Here is the configuration file you must use to run the example:
sample configuration file
From experience, I know that sometimes it's dumb to have interfaces for all your components. There are classes that are unlikely to have a different implementation, like Data Access Objects. In this case, you can register classes into the container as follows:
public class MyComponent
{
private IEmailSender _sender;
public MyComponent()
{
}
public IEmailSender EmailSender
{
get { return _sender; }
set { _sender = value; }
}
}
IWindsorContainer container = new WindsorContainer();
container.AddComponent( "mycomponent", typeof(MyComponent) );
You might want to request a component by the service:
IEmailSender emailSender = container[ typeof(IEmailSender) ] as IEmailSender;
But please note that you can register more than one implementation for a given service. The default behavior is to return the first implementation registered for the specified service. If you want a specific implementation, then you need to use the key.
As a matter of fact, the last paragraph leads us to an interesting situation. Suppose you have implemented several IEmailSender but you want that the INewsletterService implementation uses the SMTP version and nothing else. The better way to do it is to use the configuration to specify the exact implementation you want:
The nodes inside 'parameters' must use the name of a property exposed by the implementation or the name of the argument the implementation's constructor uses. Just to refresh your memory:
public class SimpleNewsletterService : INewsletterService
{
private IEmailSender _sender;
private ITemplateEngine _templateEngine;
public SimpleNewsletterService(IEmailSender sender, ITemplateEngine templateEngine)
{
_sender = sender;
_templateEngine = templateEngine;
}
No comments:
Post a Comment