going towards inversion of control and better design
DESCRIPTION
TRANSCRIPT
Going towards Inversion of Control and better design
Omar AL Zabir
Typical code
public class CustomerManager{
public void Insert(Customer customer){
CustomerData data = new CustomerData();var newCustomer = data.Insert(customer);
Logger.Log("New customer inserted" + newCustomer.Name);
Mailer.SendWelcomeMail(newCustomer);return newCustomer;
}}
Here’s a typical example of code we usually do:
A business layer class
A data access layer class
Utility class
Visualize the design
Problems?
• CustomerManager has hard dependency on CustomerData, Logger, and Mailer.
• If we decide to change the Logger class to some other class, we will have to go throw all such Manager classes and do search replace.
• We cannot unit test CustomerManager unless CustomerData properly works with database, Logger can write log to disk, Mailer has a SMTP server to send mails to.
Solution• Make CustomerManager completely
independent of any other class.class CustomerManager{
private ICustomerData _customerData;private ILogger _logger;private IMailer _mailer;public CustomerManager(ICustomerData customerData, ILogger
logger, IMailer mailer){
_customerData = customerData;_logger = logger;_mailer = mailer;
}public void Insert(Customer customer){
_customerData.Insert(cusotmer);_logger.Log("New customer inserted");_mailer.SendWelcomeMail(customer);
}}
Design is much better, less coupling
Principles
• If ClassA needs ClassB, instead of doing new ClassB, take an interface in the constructor and use that.
• Never do new Something(). As soon as you do it, you introduce a hard dependency.
• Never call static methods on your classes Logger.Log. You introduce a hard dependency.
Pains
• It’s a pain to create CustomerManager. Everytime you have to pass all the dependencies.
• Higher level needs to know what are the lower level classes. For ex, say in default.aspx:
CustomerManager manager = new CustomerManager(new CustomerData(),new Logger(),new Mailer());
Solution
• Use a default constructorpublic class CustomerManager{
private ICustomerData _customerData;private ILogger _logger;private IMailer _mailer;
public CustomerManager() : this(new CustomerData(), new Logger(), new Mailer())
{}public CustomerManager(ICustomerData
customerData, ILogger logger, IMailer mailer){
Wrong!
• You introduced hard dependency again.• Solution is to use a Container.
What’s a Container
• Container is like a registry of interfaces and their implementation.
• It stores which class is the current implementation of which interface.
• Containers – Microsoft Enterprise Library Unity, Ninject, Munq.
Registering in container
• In your Global.asax or Main function, you initialize the Container
Global.asax
Application_Start(){
Container.Register<ILogger, Logger2>();Container.Register<ICustomerData,
CustomerDataLinqToSql>();}
Using Container• The default constructor of CustomerManager resolves the
implementation of the interfaces from the Container.
public class CustomerManager{
private ICustomerData _customerData;private ILogger _logger;private IMailer _mailer;
public CustomerManager() : this(
Container.Resolve<ICustomerData>(),Container.Resolve<ILogger>(),Container.Resolve<IMailer>()
){}public CustomerManager(ICustomerData customerData,
ILogger logger, IMailer mailer){
Benefits?
• You can change the current implementation of an interface in one place and the whole project gets it.
• Say, you change the registration of ILogger to:– Customer.Register<ILogger, LoggerUsingDatabase>()
• Everyone uses logging on database.
What problems does this solve?• It makes your code unit testable.• By doing this, you make your classes independent of each
other. CustomerManager does not know who inserts record in database, who logs it, who mails it. This is good.
• When you have a defective class, you can easily take it out and replace it with a correct implementation by changing the Registration at Container.
• It makes refactoring much simpler. There’s very little coupling between classes. So, we can easily refactor code as long as the interfaces don’t change.
• We can disable certain class by replacing the implementation of an interface with a stub. For ex, if we want to disable Logging, we will register a stub for ILogger.
Common reactions to this• I have a lot of code in my project. I can’t make such drastic
changes now and miss deliveries!– Use Visual Studio. Right click on a class and select Generate
Interface. It will generate an interface for you. Then where you have done “new ClassA”, you replace it with “Container.Resolve<IClassA>()”. Voila!
• I don’t see how containers work. How does it get the right class?– Container is like a dictionary. It remembers for which interface,
which class it needs to create. When you call Container.Resolve<ISomeInterface>(), it looks at the class that was registered for this interface, say Container.Register<ISomeInterface, SomeClass>();
More common reactions• Isn’t it slower than just doing “new SomeClass”?
– Negligible. You won’t notice it unless you are doing a million “new Someclass()” in a row.
• But my class takes constructor arguments. How does Container provide the right arguments?– You can do: Container.Resolve<ICustomerData>(arg1, arg2, arg3);
• I still don’t get it. Aren’t we just changing the use of class to use of interface at the cost of added complexity? – If you are asked to change the class that does email and replace
it with another class, can you do it in less than 5 lines of code change throughout your project?
– Can you test some class in business layer while database connection is unavailable without doing more than 5 lines of code change?
How do you know you are doing it right?
• You like to implement Container, do Dependency Injection, achieve “Inversion of Control” in your project. How do you know you are doing it right?– There’s no “new SomeClass(...)” in your code
anymore.– You can mock lower layer classes and test a high
layer class in complete isolation. For ex, test CustomerManager without having database connection available.