|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionIn designing an object-oriented application, a major tenet of design is "loose coupling". Loosely, not meant for the pun, "loose coupling" means that objects should only have as many dependencies as is needed to do their job - and the dependencies should be few. Furthermore, an object's dependencies should be on interfaces and not on "concrete" objects, when possible. (A concrete object is any object created with the keyword "Dependency Injection" (DI), also more cryptically known as "Inversion of Control" (IoC), can be used as a technique for encouraging this loose coupling. There are two primary approaches to implementing DI: constructor injection and setter injection. Obviously, at some point, something must be responsible for creating the concrete objects that will be injected into another object. The injector can be a parent object, which I'll call the "DI controller", or can be externalized and handled by a "DI container" framework. What follows is a brief overview of the various approaches for using dependency injection techniques. Constructor InjectionConstructor Injection is the DI technique of passing an object's dependencies to its constructor. The below example includes a class,
The aforementioned example follows: public class Customer {
public Customer(IOrderDao orderDao) {
if (orderDao == null)
throw new ArgumentNullException("orderDao may not be null");
this.orderDao = orderDao;
}
public IList GetOrdersPlacedOn(DateTime date) {
// ... code that uses the orderDao member
// get orders from the datasource ...
}
private IOrderDao orderDao;
}
In the example, note that the constructor accepts an interface; it does not accept a concrete object. Also, note that an exception is thrown if the Setter InjectionSetter Injection does not force dependencies to be passed to the constructor. Instead, the dependencies are set onto public properties exposed by the object in need. As implied previously, the primary motivators for doing this include:
The code below modifies the Constructor Injection example to use Setter Injection instead: public class Customer {
public Customer() {}
public IOrderDao OrderDao {
set { orderDao = value; }
get {
if (orderDao == null)
throw new MemberAccessException("orderDao" +
" has not been initialized");
return orderDao;
}
}
public IList GetOrdersPlacedOn(DateTime date) {
//... code that uses the OrderDao public
//... property to get orders from the datasource ...
}
// Should not be called directly;
// use the public property instead
private IOrderDao orderDao;
}
In the above example, the constructor accepts no arguments. Instead, the invoking object is responsible for setting the Setter Injection should be used sparingly in place of Constructor Injection, because it:
The InjectorsThe next logical question is, what actually creates the dependencies that are to be injected into "injectees"? There are two appropriate places for adding creation logic: controllers and containers. DI ControllersThe "DI controller" approach is the simpler to understand and implement. In a properly tiered architecture, an application has distinct layers for handling logic. The simplest layering usually consists of a data-layer for talking to the database, a presentation-layer for displaying the UI, and a domain-logic layer for performing business logic. A "controller" layer always exists, even if not well defined, for coordinating UI events to the domain and data layers, and vice versa. For example, in ASP.NET, the code-behind page acts as a rudimentary controller layer. More formalized controller-layer approaches exist: Struts and Spring for Java; Front Controller and Spring .NET for .NET. All of these approaches follow some form of variant of the Model-View-Controller pattern. Regardless of what you use as your controller, the controller is an appropriate location for performing Dependency Injection "wiring". This is where concrete objects are created and injected as dependencies. What follows are two examples of DI performed by a controller. The first is an illustrative example of "production code" - code that you'd end up deploying. The second is an example of "test code" - code that's used to test the application, but is not deployed and does not have the need to have a live database. Controller code performing the dependency injection (e.g., from an ASP.NET code-behind page): //... code performed when the controller is loaded ...
IOrderDao orderDao = new OrderDao();
// Using Setter Injection on a pre-existing customer
someCustomer.OrderDao = orderDao;
IList ordersPlacedToday =
someCustomer.GetOrdersPlacedOn(DateTime.Now);
...
Unit-test code performing dependency injection: IOrderDao orderDao = new MockOrderDao();
// Using Setter Injection on a pre-existing customer
someCustomer.OrderDao = orderDao;
IList ordersPlacedToday =
someCustomer.GetOrdersPlacedOn(DateTime.Now);
One of the major benefits of using a DI-controller to inject dependencies is that it's straightforward and easy to point to where the creation is occurring. The drawback to using DI-controllers is that the dependencies are still hard-coded somewhere; albeit, they're hard-coded in a location that is often subject to frequent changes anyway. Another drawback is that now the DI-controllers themselves can't be easily unit-tested with mock objects. (Granted, a powerful tool such as TypeMock can do just about anything when it comes to injecting mock objects. But a tool such as TypeMock should be used only when absolutely necessary as it can lead to habits of not programming-to-interface. In fact, I'd recommend only considering the use of it on very difficult to test, legacy applications.) In ASP.NET, I prefer to use the Model-View-Presenter (MVP) pattern, and have the ASP.NET code-behind page create dependencies and inject them to the presenter via Construction Injection. Additionally, I use UserControls as the View part of the pattern, so the ASP.NET code-behind acts purely as an MVP "dependency initializer" between the UserControls (View) and their presenters. Another option to implementing constructor or setter DI is the use of an application container... DI ContainersInversion-of-Control/Dependency-Injection "containers" can be used to watch an application and inject dependencies whenever a particular event occurs. For example, whenever a Spring .NET allows you to define dependency injections within an XML file. The following example Spring .NET XML uses Setter Injection to give an ASPX code-behind page its data-access object dependency: <spring>
<context type="Spring.Context.Support.WebApplicationContext, Spring.Web">
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<!-- Data Access Object that will be injected into ASPX page -->
<object id="daoFactory" type="MyApp.Data.DaoFactory, MyApp.Data" />
<object type="ViewDetails.aspx">
<property name="DaoFactory" ref="daoFactory" />
</object>
</objects>
</spring>
The ASPX code-behind simply exposes a public property called Many other containers exist, some of which don't require much XML management at all (for those of you that cringe at the sight of a 500 line XML file). For Java developers, take a look at Container Comparison for a good comparison of options. For .NET developers, the decision is simpler as fewer options exist. (Is that a good thing?) See Open Source Inversion of Control Containers in C# for some open-source options. Unit Testing with Injected Mock ObjectsThe greatest benefits that I've experienced using DI are cleaner unit testing and greater portability. Portability is a given as most of the dependencies are on interfaces. But let's look at how DI can benefit unit testing as well. It is often the case that developers write unit tests against a live database. This is all well and fine, but it quickly brings the speed of a suite of unit tests to a crawl. To keep unit testing from becoming a drag, it needs to be easy to turn off slow unit tests that actually hit the database while still testing the business logic that depends on data access. Mock objects are perfect for doing just this. A mock object simulates the responses of an actual object, acting as if it's using a real resource. They're great for mocking access to a database, mocking calls to IO, mocking calls to a web service, etc. The below code shows a mock data-access implementation of the public class MockOrderDao : IOrderDao {
public Order GetOrderById(long orderId) {
Order foundOrder = new Order();
foundOrder.SaleDate = "1/1/06";
foundOrder.ID = orderId;
return foundOrder;
}
}
This trivial, mock data-access object implements Additional References
|
||||||||||||||||||||||