[Disclaimer]
I'm a interface based programming/IOC + dependency injection/location agnostic programming nut.
why this post
Recently, it seems that DI has become the new buzzword...
As a self confessed DI nut I don't find such an issue with this, but I'd like to present the case for it nonetheless.
Too often the usefullness of DI, nay the programming technique it represents, it looked on as a way to "make testing easier", which is just one of the benefits.
example
I'm going to try and take baby steps here, each time using a little bit of (psuedo) code to illustrate what I'm doing.
public class CustomerLogic{
private CustomerRepository repository = new CustomerRepository();
public Customer Save(Customer c){
return repository.Save(c);
}
}
So what we have here is your basic "business logic" class that allows you to save a customer.
In this it uses the customer repository class which is part of the DAL.
public class CustomerRepository {
private Logger logger = new Logger();
public Customer Save(Customer c){
logger.log("saving customer");
//save and return object
}
}
OK, so far so good, because it works *cough*.
Interface based programming
I find this a tricky one to explain as every explanation only covers a part of the picture:
- wikipedia
- An application is made up out of different, logical components.
When crossing from one type of component into another you prefer to use interfaces.
eg: CustomerLogic-> IRepository, not Repository - prefer interfaces over base classes, thus avoiding the diamond of death :)
- interfaces allow you to limit the number of members a class can call
eg: say you are trying to do webforms MVC (i'm not talking about the new MVC framework)
in this your controller needs a reference to your view (the webform), something like:
private Controller controller = new Controller(this);
If you do this with the view type, than every method on the view is visible to the controller, even stuff you'd like not to see. However, if you do this with an interface IView, then only the methods on the IView interface are visible to the controller. - interfaces allow for the "I DON'T CARE" principle
reusing the previous example, the controller doesn't care who the view is, it just needs an implementaiton of IView to work with.
Ok, so what does this mean for our running example?
public class CustomerLogic{private IRepository<Customer> repository = new CustomerRepository();
public Customer Save(Customer c){
return repository.Save(c);
}
}
public class CustomerRepository : IRepository<Customer> {
private ILog logger = new Logger();
public Customer Save(Customer c){
logger.log("saving customer");
//save and return object
}
}
public interface IRepository<T>{
T Save(T item);
}
public interface ILog {
void Log(string message);
}
So now we've ended up with
private ILog logger = new Logger();
private IRepository<Customer> repository = new CustomerRepository();
both of these still have a concrete type specified, so applying the interface made it possible for limiting the number of methods that can be called, however, my class still needs to care about who does the work.
Factory pattern
Enter the factory pattern, which gives you
private IRepository<Customer> repository ;
public CustomerLogic(){
repository = RepositoryFactory.Create<Customer>();
}
public Customer Save(Customer c){
return repository.Save(c);
}
}
public class CustomerRepository : IRepository<Customer> {
private ILog logger ;
public CustomerRepository(){
logger = LoggerFactory.Create();
}
public Customer Save(Customer c){
logger.log("saving customer");
//save and return object
}
}
public static class RepositoryFactory{
public static IRepository Create<T>(){
if( typeof(T).Equals(typeof(Customer)){return new CustomerRepository();}
}
}
public static class LoggerFactory{
public static ILog Create(){
return new Logger();
}
}
So what has happened here?
- we have split out any concrete type information fromthe logic class.
Finally our logic class doesn't have to care who does the work any more! - all creational logic is encapsulated in a factory, so changing what kind of type is created can be done in one place.
There's only one problem...
The factory as presented above expects you to know any type you want to return at the moment you are writing the code.
This means that if after deployment you want to change that type, you need to recompile your application.
While this is acceptable in most scenario's it's not always, so here's the next silver bullet: the dynamic factory pattern.
An easy fix for this is to use a configuration driven approach, wherein the type to load is specified via the app/web.config.
About a million ways exist to do this, but mainly I used to go for the custom configuration element approach.
In the end it all comes down to
public static class RepositoryFactory{
public static IRepository Create<T>(){
// check to see if you already have this object, for instance if it's already instantiated and so on
// life time management is a topic in it's own right..
T instance = ObjectFactory.Create<IRepository<T>>();
// save the instance?
return instance;
}
}
public class ObjectFactory{
public static T Create<T>(string type){
Type t = Type.Load("yourtype's fullname / assembly qualified name here");
return System.Activator.CreateInstance(t) as T;
}
}
which is the configurable approach..
Again, while this is a lot better than the previous approach, it far from perfect.
The main issue is that the RepositoryFactory doens't really have a raison d'être anymore.
Service Locator
Enter the service locator pattern, which is the factory to rule them all ;)
public static class ServiceLocator{
public static T Create<T>(){
//life time management
// if type not found, create a new one and return
T instance = ObjectFactory.Create<T>();
// life time manangement
return instance;
}
}
public class CustomerLogic{
private IRepository<Customer> repository ;
public CustomerLogic(){
repository = ServiceLocator.Create<IRepository<Customer>>();
}
public Customer Save(Customer c){
return repository.Save(c);
}
}
private ILog logger ;
public CustomerRepository(){
logger = ServiceLocator.Create<ILog>();
}
public Customer Save(Customer c){
logger.log("saving customer");
//save and return object
}
}
less code (no more dedicated factories), me happy
Dependency injection
So what is left for us to tackle?
The biggest issue that still remains is the dependencies we need to manually resolve:
private IRepository<Customer> repository ;
public CustomerLogic(){
repository = ServiceLocator.Create<IRepository<Customer>>();
}
public CustomerRepository(){
logger = ServiceLocator.Create<ILog>();
}
Wouldn't it be great if this could have been done automatically?
Unsurprisingly, this is what *your favorite dependency injection container* does.
if we take Unity as an example, you either decorate properties with the [Dependency] attribute, or have one constructor (which is then used for DI) or have a [InjectionConstructor] attribute on it.
public class CustomerLogic{
[Dependency]
public IRepository<Customer> repository {get;set}
public Customer Save(Customer c){
return repository.Save(c);
}
}
private ILog logger ;
public CustomerRepository(ILog log){
logger = log;
}
public Customer Save(Customer c){
logger.log("saving customer");
//save and return object
}
}
so now your usage becomes
UnityContainer container = new UnityContainer();
CustomerLogic logic = container.Create(CustomerLogic);
logic.Save(customer);
So, what is the object of Injection
Recently, I talked to an ex collegae who asked me what I consider to be an appropriate target for DI?
- never never never domain objects
- anything which you can call a "logical service", which you can "hide" behind an interface. (service is a badly overused words these days)
eg: a data layer, an external service, and so on.
This is also my major gripe with the entlib IV daab, on which I have a post here.
Entlib IV doens't resolve a "logical service", rather it resolves an instance that should be returned by a "logical service" => the Database object.
Conclusion
At the beginning of this post I mentionned that all too IOC/DI are considered to be minor enhancements for testability/mocking.
However, if you've been paying attention, you should have noticed that we have been able to influence the returned type from the dynamic factory onward.
I hope that this post has shown you that DI actually emerged from the other patterns (factory, dynamic factory, service locator) we have been using for years.
If you would, take a look at the other posts on Unity I have made.
Consider if you will that a DI container allows you to build interface driven component - based systems, where the container becomes the central point for resolving other objects.
0 comments:
Post a Comment