Dependency Injection and Inversion of Control Developing flexible
Dependency Injection and Inversion of Control Developing flexible, reusable and testable software Part 1 Nick Hines March 2006 © Thought. Works, 2006
Loosely Coupled Systems • Good OO Systems – organised as web of interacting objects • Goal – High cohesion, low coupling • Advantages of low coupling • Not so easy to achieve! – Extensibility – Testability – Reusability © Thought. Works, 2006
A Concrete Example – A Trade Monitor © Thought. Works, 2006
Trade Monitor – The design public class Trade. Monitor { private Limit. Dao limit. Dao; public class Limit. Dao { public int Get. Exposure(string symbol) { // Do something with the database } public Trade. Monitor() { limit. Dao = new Limit. Dao(); } } public bool Try. Trade(string symbol, int amount) { int limit = limit. Dao. Get. Limit(symbol); int exposure = limit. Dao. Get. Exposure(symbol); return (exposure + amount > limit) ? false : true; } } public int Get. Limit(string sysmbol) { // Do something with the database } • Trade. Monitor is coupled to Limit. Dao – 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. . . © Thought. Works, 2006
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 ILimit. Repository { int Get. Exposure(string symbol); int Get. Limit(string symbol); } public class Trade. Monitor { private ILimit. Repository limit. Repository; public Trade. Monitor() { limit. Repository = new Limit. Dao(); } } public bool Try. Trade(string symbol, int amount) {. . . } © Thought. Works, 2006
Trade Monitor – The Design Refactored (2) • Introduce Factory • Trade. Monitor decoupled from Limit. Dao • Limit. Dao still tightly-coupled albeit to Factory Limit. Factory <<creates>> Trade. Monitor Limit. Dao public class Limit. Factory { public static ILimit. Repository Get. Limit. Repository() { return new Limit. Dao(); return Limit. Dao(); } } public class Trade. Monitor { private ILimit. Repository limit. Repository; public Trade. Monitor() { limit. Repository = Limit. Factory. Get. Limit. Repository(); } <<interface>> Limit. Repository } public bool Try. Trade(string symbol, int amount) {. . . } © Thought. Works, 2006
Trade Monitor – The Design Refactored (3) • Introduce Service. Locator • This gives us extensibility, testability, reusability public class Service. Locator { public static void Register. Service(Type type, object impl) {. . . } public static object Get. Service(Type type) {. . . } } public class Trade. Monitor { private ILimit. Repository limit. Repository; public Trade. Monitor() { object o = Service. Locator. Get. Service(typeof(ILimit. Repository)); limit. Repository = o as ILimit. Repository; } } public bool Try. Trade(string symbol, int amount) {. . . } © Thought. Works, 2006
Service. Locator - Problems • Sequence dependence • Cumbersome setup in tests • Service depends on infrastructure code, (Service. Locator) • Code needs to handle lookup problems • Aren’t these problem minor? Why settle for something we know has issues? © Thought. Works, 2006
A Different View • What about adding a setter and let something else worry about creation and resolution? public class Trade. Monitor { private ILimit. Repository limit. Repository; public Trade. Monitor() { } } This is Setter Dependency Injection public ILimit. Repository Limits { set { limit. Repository = value; } } • The dependencies are injected from the outside • Components are passive and are not concerned with locating or creating dependencies © Thought. Works, 2006
Another Idea • Why not just use the constructor? public class Trade. Monitor { private ILimit. Repository limit. Repository; } public Trade. Monitor(ILimit. Repository limit. Repository) { this. limit. Repository = limit. Repository; } This is Constructor Dependency 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 © Thought. Works, 2006
What about Inversion of Control? • Dependency Injection - one example of Io. C 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. © Thought. Works, 2006
Io. C 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? • Io. C Containers solve these issues – Have configuration – often external – Create objects – Ensure all dependencies are satisfied – Provide lifecycle support © Thought. Works, 2006
It Gets Better • We can use reflection to determine dependencies – no need for config files. • Most Io. C 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. © Thought. Works, 2006
Io. C Containers and Features © Thought. Works, 2006
The Solution – Test Case [Test. Fixture] public class Trade. Monitor. Test { [Test] public void Monitor. Blocks. Trades. When. Limit. Exceeded() { Dynamic. Mock mock. Repository = new Dynamic. Mock(typeof(ILimit. Repository)); mock. Repository. Setup. Result('Get. Limit', 1000000, new Type[] { typeof(string) }); mock. Repository. Setup. Result('Get. Exposure', 999999, new Type[] { typeof(string) }); Trade. Monitor monitor = new Trade. Monitor((ILimit. Repository)mock. Repository. Mock. Instance); Assert. Is. False(monitor. Try. Trade('MSFT', 1000), 'Monitor should block trade'); } } public class Trade. Monitor { private ILimit. Repository repository; public Trade. Monitor(ILimit. Repository repository) { this. repository = repository; } } public bool Try. Trade(string symbol, int amount) { int limit = repository. Get. Limit(symbol); int exposure = repository. Get. Exposure(symbol); return ((amount + exposure) <= limit); } © Thought. Works, 2006
The Solution – Using the Windsor Container • Code configuration IWindsor. Container container = new Windsor. Container(); container. Add. Component('limit. Repository', typeof(ILimit. Repository), typeof(Limit. Dao)); container. Add. Component('trade. Monitor', typeof(Trade. Monitor)); Trade. Monitor monitor = (Trade. Monitor)container['trade. Monitor']; monitor. Try. Trade('MSFT', 1000); • External configuration IWindsor. Container container = new Windsor. Container('config. xml'); Trade. Monitor monitor = (Trade. Monitor)container['trade. Monitor']; monitor. Try. Trade('MSFT', 1000); <configuration> <components> <component id='limit. Repository' service='AAABank. ILimit. Repository, AAABank' type='AAABank. Limit. Dao, AAABank' /> <component id='trade. Monitor' type='AAABank. Trade. Monitor, AAABank' /> </components> </configuration> © Thought. Works, 2006
The Solution – Complex Configuration • What if components take parameters? public class Limit. Dao { public Limit. Dao(string connection. String) {…} } <configuration> <components> <component id='limit. Repository' service='AAABank. ILimit. Repository, AAABank' type='AAABank. Limit. Dao, AAABank'> <connection. String>Data Source=AServer; Initial Catalog=Bank. DB; User ID=sa</connection. String> </component>. . . public class Trade. Monitor { public Trade. Monitor(string[] monitored. Symbols) {…} } <configuration> <components> <component id='trade. Monitor' type='AAABank. Trade. Monitor, AAABank'> <monitored. Symbols> <array> <elem>MSFT</elem> <elem>TWUK</elem> </array> </monitored. Symbols>. . . © Thought. Works, 2006
Many other possibilities • Container creates objects – but what objects? • Can return proxy – no need for Marshal. By. Ref inheritance. • Object instance caching. • Aspect Oriented Programming. • Remoting by configuration. • Automatic Web Service creation. • . . . © Thought. Works, 2006
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 © Thought. Works, 2006
- Slides: 19