going towards inversion of control and better design

18
Going towards Inversion of Control and better design Omar AL Zabir

Upload: oazabir

Post on 15-Jan-2015

839 views

Category:

Documents


0 download

DESCRIPTION

 

TRANSCRIPT

Page 1: Going towards inversion of control and better design

Going towards Inversion of Control and better design

Omar AL Zabir

Page 2: Going towards inversion of control and better design

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

Page 3: Going towards inversion of control and better design

Visualize the design

Page 4: Going towards inversion of control and better 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.

Page 5: Going towards inversion of control and better design

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);

}}

Page 6: Going towards inversion of control and better design

Design is much better, less coupling

Page 7: Going towards inversion of control and better design

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.

Page 8: Going towards inversion of control and better design

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());

Page 9: Going towards inversion of control and better design

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){

Page 10: Going towards inversion of control and better design

Wrong!

• You introduced hard dependency again.• Solution is to use a Container.

Page 11: Going towards inversion of control and better design

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.

Page 12: Going towards inversion of control and better design

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>();}

Page 13: Going towards inversion of control and better design

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){

Page 14: Going towards inversion of control and better design

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.

Page 15: Going towards inversion of control and better design

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.

Page 16: Going towards inversion of control and better design

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>();

Page 17: Going towards inversion of control and better design

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?

Page 18: Going towards inversion of control and better design

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.