Section 1.2. Using Dependency Injection

team bbl


1.2. Using Dependency Injection

When I begin to build any application, it's usually tightly coupled. That's okay. I can always refactor it later. In this example, I'll structure the application so that it will work with Spring when I add it. I introduce an interface to the façade layer, so that I can have a façade layer that implements several different strategies.

The first step to learning Spring is the dependency injection pattern. It's not complicated, but it's the central concept. It's different enough from the typical way that most people code that you'll want to get it down quickly.

Figure 1-1 shows a client and server prepared for dependency injection. A client uses another class that we'll call a service. The client has a property that accepts a service. The service is wrapped in an interface. The client can't see the implementation of the service. But this code is not yet loosely coupled: you still have to create the service somewhere. With dependency injection, a third-party, called the assembler or container, creates both the client and service, and then sets the value of aService (which is a reference to an instance of Service) to satisfy the dependency.

Figure 1-1. This client uses a service wrapped in an interface



Note: Many see dependency injection for the first time and wonder, "What's the big deal? " After working with it some, they come to understand that this simple change can dramatically improve their code and ease future maintenance.

You probably already code this way in spots. You'll see how powerful this programming model can be when you use it in a framework that applies it in a consistent way across your application. Code made of decoupled components is easier to test, maintain, and understand.

1.2.1. How do I do that?

You don't have to use a lightweight container to use this design pattern. To decouple with dependency injection, there are three steps:

  1. Wrap the service in an interface.

  2. Add a property to the client, to refer to the service.

  3. With a third party framework or custom code, build the service and populate the property.

The first step is to extract the interface. We'll rename the RentABike file, class definition, and constructor to ArrayListRentABike (Example 1-4), and create an interface (Example 1-5).

Example 1-4. ArrayListRentABike.java (renamed from RentABike.java)
import java.util.*; public class ArrayListRentABike implements RentABike {     private String storeName;     final List bikes = new ArrayList( );     public ArrayListRentABike( ) { initBikes( ); }     public ArrayListRentABike(String storeName) {         this.storeName = storeName;         initBikes( ); } public void initBikes( ) {     bikes.add(new Bike("Shimano", "Roadmaster", 20, "11111", 15, "Fair"));     bikes.add(new Bike("Cannondale", "F2000 XTR", 18, "22222", 12, "Excellent"));     bikes.add(new Bike("Trek", "6000", 19, "33333", 12.4, "Fair")); } public String toString( ) { return "RentABike: " + storeName; } public List getBikes( ) { return bikes; } public Bike getBike(String serialNo) {     Iterator iter = bikes.iterator( );     while(iter.hasNext( )) {         Bike bike = (Bike)iter.next( );         if(serialNo.equals(bike.getSerialNo( ))) return bike;     }         return null;     } }

Example 1-5. RentABike.java
import java.util.*; interface RentABike {     List getBikes( );     Bike getBike(String serialNo); }

Next, Example 1-6 gives the view. Note that we've extracted the methods that print out the bikes to the command line. We also add a property to the view, which uses the RentABike interface.

Example 1-6. CommandLineView.java
import java.util.*; public class CommandLineView {     private RentABike rentaBike;     public CommandLineView( ) { }     public void setRentaBike(RentABike rentaBike) {         this.rentaBike = rentaBike;     }     public RentABike getRentaBike( ) { return this.rentaBike;}     public void printAllBikes( ) {         System.out.println(rentaBike.toString( ));         Iterator iter = rentaBike.getBikes( ).iterator( );         while(iter.hasNext( )) {             Bike bike = (Bike)iter.next( );             System.out.println(bike.toString( ));         }     } }

Finally, we have an assembler that creates each object, and sets the property (Example 1-7).

Example 1-7. RentABikeAssembler.java
public class RentABikeAssembler {     public static final void main(String[] args) {         CommandLineView clv = new CommandLineView( );         RentABike rentaBike = new ArrayListRentABike("Bruce's Bikes");         clv.setRentaBike(rentaBike);         clv.printAllBikes( );     } }

Next, you'll compile the application like this:


Note: You can see the dependency injection happening here. Third-party codecustom code in this casecreates both objects, and injects the value of RentABike into the view.
C:\RentABikeApp\src> javac -d ../out *.java

Your output directory now contains the compiled class files.

Directory of C:\RentABikeApp\out 07/28/2004  10:12 AM    <DIR>          . 07/28/2004  10:12 AM    <DIR>          .. 07/28/2004  10:50 AM             1,475 ArrayListRentABike.class 07/28/2004  10:50 AM             1,753 Bike.class 07/28/2004  10:50 AM               186 RentABike.class 07/28/2004  10:50 AM               495 RentABikeAssembler.class 07/28/2004  10:50 AM               944 CommandLineView.class

Run the application like this:

C:\RentABikeApp\out>java RentABikeAssembler RentABike: Bruce's Bikes Bike : manufacturer -- Shimano : model -- Roadmaster : frame -- 20 : serialNo -- 11111 : weight -- 15.0 : status -- Fair. Bike : manufacturer -- Cannondale : model -- F2000 XTR : frame -- 18 : serialNo -- 22222 : weight -- 12.0 : status -- Excellent. Bike : manufacturer -- Trek : model -- 6000 : frame -- 19 : serialNo -- 33333 : weight -- 12.4 : status -- Fair.

1.2.2. What just happened?

You just saw an example of dependency injection, outside of a lightweight container. The lightweight container community makes a whole lot of noise, but the ideas behind the movement are simple. Program using interfaces, and let a third party inject the dependency instead of setting it yourself.

You're going to eventually replace the assembler with Spring. When that happens, Spring will actually fire the constructors for our objects and set the dependencies. But first, you'll need to give this build a little attention.

1.2.3. What about...

...service locators, or factory objects? Of course, dependency injection is not the only way to manage dependencies. In fact, it's not the only good way. J2EE users tend to use service locators. Using that pattern (shown in Figure 1-2), you take a dependency, wrap it in an interface, register it in a dictionary, and then look it up using a helper class called a locator. It's not a bad approach.

Figure 1-2. J2EE applications manage dependencies with service locators


The dependency injection strategy lets you pick a consistent approach, and separate the dependency completely from your application. You'll see how this approach will help you test, and help you build applications that are easy to customize and configure.

In fact, earlier lightweight containers, like Avalon, used this approach. Most modern containers provide dependency lookup, but prefer other types of dependency resolution.

    team bbl



    Spring. A developer's Notebook
    Spring: A Developers Notebook
    ISBN: 0596009100
    EAN: 2147483647
    Year: 2005
    Pages: 90

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net