Wednesday, October 14, 2009

Not ready for Inversion Of Control, just use a factory for now

There has been a lot of discussion latterly in the blog world about using inversion of control or IOC, some claiming it’s to hard to understand, it complicates development, and it’s unnecessary; the other side saying quit complaining it’s not that hard, it enforces good design and makes code more maintainable. To this I’m going to take the vary controversial position that they are both right.

Inversion of control can be intimidation the first time you use it, it adds a bit of complexity and you can write quality applications with out it. On the other side once you “get it” IOC really isn't that hard it encourages separation of responsibilities, and makes it much easier to test making regression testing and refactoring much easier.

So how do we get all of the advantages with out adding all of the complexities of an IOC container, simple just use a software factory class. For those who don’t know, basically a factory class is just away to abstract and consolidate the logic for getting your code dependencies.

Let’s say you have some code for accessing data from Amazon’s simpleDB

   1: public class ContactData : IContactData
   2: {
   3:     private Proxy simpleDBProxy;
   4:     public Proxy SimpleDBProxy
   5:     {
   6:         get
   7:         {
   8:             if (simpleDBProxy==null)
   9:             {
  10:                 simpleDBProxy = new Proxy();
  11:             }
  12:             return simpleDBProxy;
  13:         }
  14:         set
  15:         {
  16:             simpleDBProxy = value;
  17:         }
  18:     }
  19:     const string DomainName = "Contacts";
  20:  
  21:     public Contacts GetContacts()
  22:     {
  23:         Contacts myContacts = new Contacts();
  24:  
  25:         SelectRequest request = new SelectRequest
  26:         {
  27:             SelectExpression = string.Format("SELECT * FROM {0} ", DomainName)
  28:         };
  29:         SelectResponse response = SimpleDBProxy.Service.Select(request);
  30:  
  31:         var contacts = from item in response.SelectResult.Item
  32:                          select new Contact()
  33:                                     {
  34:                                         Email = item.Attribute.GetValueByName("Email"),
  35:                                         Name = item.Attribute.GetValueByName("Name"),
  36:                                         Phone = item.Attribute.GetValueByName("Phone"),
  37:                                         ID =  item.Name
  38:                                     };
  39:         myContacts.AddRange(contacts);
  40:         return myContacts;
  41:     }
  42:  
  43:     public Contacts GetContactsByName(string contactName)
  44:     {
  45:         Contacts myContacts = new Contacts();
  46:  
  47:         SelectRequest request = new SelectRequest
  48:         {
  49:             SelectExpression = string.Format("SELECT * FROM {0} where Name='{1}' ", DomainName, contactName)
  50:         };
  51:         SelectResponse response = SimpleDBProxy.Service.Select(request);
  52:  
  53:         var contacts = from item in response.SelectResult.Item
  54:                        select new Contact()
  55:                        {
  56:                            Email = item.Attribute.GetValueByName("Email"),
  57:                            Name = item.Attribute.GetValueByName("Name"),
  58:                            Phone = item.Attribute.GetValueByName("Phone"),
  59:                            ID = item.Name
  60:                        };
  61:         myContacts.AddRange(contacts);
  62:         return myContacts;
  63:     }
  64:    
  65:     public bool SaveContact(Contact contact)
  66:     {
  67:         List<ReplaceableAttribute> attributeList = new List<ReplaceableAttribute>
  68:            {
  69:                new ReplaceableAttribute().WithName("Email").WithValue(contact.Email),
  70:                new ReplaceableAttribute().WithName("Name").WithValue(contact.Name),
  71:                new ReplaceableAttribute().WithName("Phone").WithValue(contact.Phone)
  72:            };
  73:         contact.ID = Guid.NewGuid().ToString();
  74:         bool success = false;
  75:         try
  76:         {
  77:             if (!SimpleDBProxy.Domains.Contains(DomainName))
  78:             {
  79:                 SimpleDBProxy.AddDomain(DomainName);
  80:             }
  81:             PutAttributesRequest action = new PutAttributesRequest
  82:               {
  83:                   ItemName = contact.ID,
  84:                   Attribute = attributeList,
  85:                   DomainName = DomainName
  86:               };
  87:             PutAttributesResponse response = SimpleDBProxy.Service.PutAttributes(action);
  88:             success = true;
  89:         }
  90:         catch (Exception requestException)
  91:         {
  92:             success = false;
  93:         }
  94:  
  95:         return success;
  96:     }
  97: }

now because this implements the IContactData interface

   1: public interface IContactData
   2: {
   3:     Contacts GetContacts();
   4:     Contacts GetContactsByName(string contactName);
   5:     bool SaveContact(Contact contact);
   6: }

we can use it anywhere we would use IContactData, for example here we have an application that uses both local and remote contacts

   1: public class ContactsRequests
   2: {
   3:     private IContactData ContactData;
   4:     public bool UseLocalContacts { get;set; }
   5:  
   6:     public Contacts SearchContactsByName(string contactName)
   7:     {
   8:         if (UseLocalContacts)
   9:         {
  10:             ContactData = new LocalContactData();
  11:         }
  12:         else
  13:         {
  14:             ContactData = new ContactData();
  15:         }
  16:         return ContactData.GetContactsByName(contactName);
  17:     }
  18:  
  19:     public Contacts GetContacts()
  20:     {
  21:         if (UseLocalContacts)
  22:         {
  23:             ContactData = new LocalContactData();
  24:         }
  25:         else
  26:         {
  27:             ContactData = new ContactData();
  28:         }
  29:         return ContactData.GetContacts();
  30:     }
  31:  
  32:     public bool AddContact(Contact newContact)
  33:     {
  34:         return ContactData.SaveContact(newContact);
  35:     }
  36: }

now this works but there are a few problems, first there is no way to test it’s functionality without testing the functionality of LocalContactData and/or ContactData with a little simple refactoring

   1: public class ContactsRequests
   2: {
   3:     private IContactData contactData;
   4:     public IContactData ContactData
   5:     {
   6:         get
   7:         {
   8:             if (contactData==null)
   9:             {
  10:                 if (UseLocalContacts)
  11:                 {
  12:                     contactData = new LocalContactData();
  13:                 }
  14:                 else
  15:                 {
  16:                     contactData = new ContactData();
  17:                 }
  18:             }
  19:             return contactData;
  20:         }
  21:         set
  22:         {
  23:             contactData = value;
  24:         }
  25:     }
  26:     public bool UseLocalContacts { get;set; }
  27:  
  28:     public Contacts SearchContactsByName(string contactName)
  29:     {          
  30:         return ContactData.GetContactsByName(contactName);
  31:     }
  32:  
  33:     public Contacts GetContacts()
  34:     {
  35:        return ContactData.GetContacts();
  36:     }
  37:  
  38:     public bool AddContact(Contact newContact)
  39:     {
  40:         return ContactData.SaveContact(newContact);
  41:     }
  42: }
we remove the instantiation logic from the method and move it to a public property this way we can set the ContactData property to a mock object and test the specific logic of the methods in this class, now the only problem with this is everywhere we want to use IContactData we have to perform the same logic, and basically reproduce the same code over and over again. This is where factories come in, if we build a factory like this
   1: public static class DataFactories
   2: {
   3:     public static IContactData GetContactInterface(bool isLocal)
   4:     {
   5:         IContactData myContactData;
   6:         if (isLocal)
   7:         {
   8:             myContactData = new LocalContactData();
   9:         }
  10:         else
  11:         {
  12:             myContactData= new ContactData();
  13:         }
  14:         return myContactData;
  15:     }
  16: }

Then we just implement it like this

   1: public IContactData ContactData
   2: {
   3:     get
   4:     {
   5:         if (contactData==null)
   6:         {
   7:             contactData = DataFactories.GetContactInterface(UseLocalContacts);
   8:         }
   9:         return contactData;
  10:     }
  11:     set
  12:     {
  13:         contactData = value;
  14:     }
  15: }

your applications don’t know or care how they got there class for getting contact all they know is they did, where this really comes in handy is if you have to change out how you handle your local contacts from let’s say an xml file to a database, the only place you have to change your code is in the factory.

Congratulations you are now 90% of the way there for using a IOC container, you have a single place for handling all of your instantiation so once you decide to use IOC you just change how the factory works from having logic that decides what class to insatiate to pulling it from the IOC container, it’s that simple.

Truth be told I always start with a factory and then later “IF” I need the functionality of an IOC I’ll add it. In the end an IOC container really isn’t nearly as scary as it seems to be, and in the same token it isn’t nearly a vital as some people would like to claim, it’s a tool that can be used to solve specific problems, but 90% of the same problems can be solved using a factory.


1 comment:

Unknown said...

Nice article. This is why I use castle though. With the wizards it's easy to create MVC projects with Windsor as the IOC container, monorail as the MVC, and ActiveRecord or NHibernate as the ORM. Pretty slick if you have a service layer object you want to pass to multiple controllers because the IOC container handles all dependencies.