310 likes | 688 Views
Dependency Injection and Inversion of Control. Developing flexible, reusable and testable software Part 1 Nick Hines March 2006. Loosely Coupled Systems. Good OO Systems – organised as web of interacting objects Goal – High cohesion, low coupling. Advantages of low coupling Extensibility
E N D
Dependency Injection and Inversion of Control Developing flexible, reusable and testable software Part 1 Nick Hines March 2006
Loosely Coupled Systems • Good OO Systems – organised as web of interacting objects • Goal – High cohesion, low coupling • Advantages of low coupling • Extensibility • Testability • Reusability • Not so easy to achieve!
Trade Monitor – The design • TradeMonitor is coupled to LimitDao – this is not good! • Extensibility – what if not database but distributed cache • Testability – where do the limits for test come from? • Reusability – logic is fairly generic . . . public class TradeMonitor { private LimitDao limitDao; public TradeMonitor() { limitDao = new LimitDao(); } public bool TryTrade(string symbol, int amount) { int limit = limitDao.GetLimit(symbol); int exposure = limitDao.GetExposure(symbol); return (exposure + amount > limit) ? false : true; } } public class LimitDao { public int GetExposure(string symbol) { // Do something with the database } public int GetLimit(string sysmbol) { // Do something with the database } } limitDao = new LimitDao();
Trade Monitor – The Design Refactored (1) • Introduce interface/implementation separation • Logic does not depend on DAO anymore. • Does this really solve the problem? • The constructor still has a static dependency on DAO public interface ILimitRepository { int GetExposure(string symbol); int GetLimit(string symbol); } public class TradeMonitor { private ILimitRepository limitRepository; public TradeMonitor() { limitRepository = new LimitDao(); } public bool TryTrade(string symbol, int amount) { . . . } } limitRepository = new LimitDao();
Trade Monitor – The Design Refactored (2) • Introduce Factory • TradeMonitor decoupled from LimitDao • LimitDao still tightly-coupled albeit to Factory public class LimitFactory { public static ILimitRepository GetLimitRepository() { return new LimitDao(); } } public class TradeMonitor { private ILimitRepository limitRepository; public TradeMonitor() { limitRepository = LimitFactory.GetLimitRepository(); } public bool TryTrade(string symbol, int amount) { . . . } } LimitFactory return new LimitDao(); <<creates>> TradeMonitor LimitDao <<interface>> LimitRepository
Trade Monitor – The Design Refactored (3) • Introduce ServiceLocator • This gives us extensibility, testability, reusability public class ServiceLocator { public static void RegisterService(Type type, object impl) {. . .} public static object GetService(Type type) {. . .} } public class TradeMonitor { private ILimitRepository limitRepository; public TradeMonitor() { object o = ServiceLocator.GetService(typeof(ILimitRepository)); limitRepository = o as ILimitRepository; } public bool TryTrade(string symbol, int amount) { . . . } }
ServiceLocator - Problems • Sequence dependence • Cumbersome setup in tests • Service depends on infrastructure code, (ServiceLocator) • Code needs to handle lookup problems • Aren’t these problem minor? Why settle for something we know has issues?
A Different View • What about adding a setter and let something else worry about creation and resolution? public class TradeMonitor { private ILimitRepository limitRepository; public TradeMonitor() { } public ILimitRepository Limits { set { limitRepository = value;} } } This is SetterDependency Injection • The dependencies are injected from the outside • Components are passive and are not concerned with locating or creating dependencies
Another Idea • Why not just use the constructor? public class TradeMonitor { private ILimitRepository limitRepository; public TradeMonitor(ILimitRepository limitRepository) { this.limitRepository = limitRepository; } } This is ConstructorDependency Injection • No setters for dependent components, (obviously) • One-shot initialisation – components are always initialised correctly • All dependencies are clearly visible from code • It is impossible to create cyclic dependencies
What about Inversion of Control? • Dependency Injection - one example of IoC design principle. • Also known as the Hollywood Principle • Don’t call us, we’ll call you! • Objects rely on their environment to provide dependencies rather than actively obtaining them. • Inversion of Control can make the difference between a library and a framework.
IoC Containers • There are still some open questions • Who creates the dependencies? • What if we need some initialisation code that must be run after dependencies have been set? • What happens when we don’t have all the components? • IoC Containers solve these issues • Have configuration – often external • Create objects • Ensure all dependencies are satisfied • Provide lifecycle support
It Gets Better • We can use reflection to determine dependencies – no need for config files. • Most IoC containers support auto-wiring. • Make components known to container. • Container examines constructors and determines dependencies. • Auto-wiring provides other benefits. • Less typing, especially long assembly names. • Static type checking by IDE at edit time. • More intuitive for developer.
The Solution – Test Case [TestFixture] public class TradeMonitorTest { [Test] public void MonitorBlocksTradesWhenLimitExceeded() { DynamicMock mockRepository = new DynamicMock(typeof(ILimitRepository)); mockRepository.SetupResult('GetLimit', 1000000, new Type[] { typeof(string) }); mockRepository.SetupResult('GetExposure', 999999, new Type[] { typeof(string) }); TradeMonitor monitor = new TradeMonitor((ILimitRepository)mockRepository.MockInstance); Assert.IsFalse(monitor.TryTrade('MSFT', 1000), 'Monitor should block trade'); } } public class TradeMonitor { private ILimitRepository repository; public TradeMonitor(ILimitRepository repository){this.repository = repository; } public bool TryTrade(string symbol, int amount) { int limit = repository.GetLimit(symbol); int exposure = repository.GetExposure(symbol); return ((amount + exposure) <= limit); } }
The Solution – Using the Windsor Container • Code configuration IWindsorContainer container = new WindsorContainer(); container.AddComponent('limitRepository', typeof(ILimitRepository), typeof(LimitDao)); container.AddComponent('tradeMonitor', typeof(TradeMonitor)); TradeMonitor monitor = (TradeMonitor)container['tradeMonitor']; monitor.TryTrade('MSFT', 1000); • External configuration IWindsorContainer container = new WindsorContainer('config.xml'); TradeMonitor monitor = (TradeMonitor)container['tradeMonitor']; monitor.TryTrade('MSFT', 1000); <configuration> <components> <component id='limitRepository' service='AAABank.ILimitRepository, AAABank' type='AAABank.LimitDao, AAABank' /> <component id='tradeMonitor' type='AAABank.TradeMonitor, AAABank' /> </components> </configuration>
The Solution – Complex Configuration • What if components take parameters? public class LimitDao { public LimitDao(string connectionString) {…} } <configuration> <components> <component id='limitRepository' service='AAABank.ILimitRepository, AAABank' type='AAABank.LimitDao, AAABank'> <connectionString>Data Source=AServer;Initial Catalog=BankDB;User ID=sa</connectionString> </component> . . . public class TradeMonitor { public TradeMonitor(string[] monitoredSymbols) {…} } <configuration> <components> <component id='tradeMonitor' type='AAABank.TradeMonitor, AAABank'> <monitoredSymbols> <array> <elem>MSFT</elem> <elem>TWUK</elem> </array> </monitoredSymbols> . . .
Many other possibilities • Container creates objects – but what objects? • Can return proxy – no need for MarshalByRef inheritance. • Object instance caching. • Aspect Oriented Programming. • Remoting by configuration. • Automatic Web Service creation. • . . .
Summary • Container based DI facilitates: - • Testability • Extensibility • Reusability • Makes the difference between framework and library • Not just use but extend • Essential for complex Domain Driven Design • Easier to separate 'infrastructure' from business logic