The Source for Java Technology Collaboration
User: Password:



   

Breaking the Last Dependency Breaking the Last Dependency

by Elisabeth Freeman and Eric Freeman
04/14/2005

Contents
Setting the Stage
Factory Refresher
Let's Break that Last Dependency
Taking It All the Way
Summary
Complete Code

As Head First Design Patterns was about to go to press, Erich Gamma sent us a note suggesting that in the factory pattern chapter we should break the last dependency and show how to write code that does away with concrete classes completely. He was right on--that was the next logical step, but because of size and time constraints, sadly that topic just didn't get into the book. But on java.net, we have no such constraints, so let's tackle this topic now.

Setting the Stage

First let's work through what "breaking the last dependency" means, how it relates to the factory pattern, and why you should care. First, all of the factory patterns "encapsulate" the instantiation of concrete classes and help to minimize (as well as localize) the dependencies your code has on those concrete classes. What does that mean? Consider the following code:


public class StampedeSimulator {
    Actor actor = null;

    public void addActor(String type) {

        if (type.equals("buffalo")) {
            actor = new Buffalo();
        } else if (type.equals("horse")) {
            actor = new Horse();
        } else if (type.equals("cowboy")) {
            actor = new Cowboy();
        } else if (type.equals("cowgirl")) {
            actor = new Cowgirl();
        }

        // rest of simulator here
    }
}

This code is tied to four different concrete classes (Buffalo, Horse, Cowboy, and Cowgirl) and, as a result, creates a dependency between your code and these concrete classes. Why is that a bad thing? Well, whenever you add a new type (say, a Coyote) or reconfigure the concrete classes (say you want to use the FastHorse class instead of the generic Horse class) you'll have to rework this code (read: high maintenance). Keep in mind you might have similar concrete instantiations sprinkled all around your code, and so you're going to have to change this code in multiple places (in other words, you're setting yourself up for lots of bugs). Note that we could also clean this code up and make it more type-safe by using Java 5's enumerations instead of matching strings, but since everyone isn't on Java 5 yet (like you Mac users), we'll leave that exercise for another time.

OOCode

Now, what if we had a way to minimize the dependency on the concrete classes? This would make your life easier by reducing the amount of code you have to maintain, and give you time for all of that other stuff you'd rather be doing anyway. That's where factories come in.

Factory Refresher

There are several kinds of factories, which you can look up in any patterns book. For the purposes of demonstration, let's take a look at a Static Factory, which consists of a class that provides a static method to handle the instantiation of an object. To implement this, we put all of the instantiation code into a factory, ActorFactory, and replace this code in the StampedeSimulator with code that uses the factory to create the objects:


public class ActorFactory {
    static public Actor createBuffalo() {
        return new Buffalo();
    }
    static public Actor createHorse() {
        return new Horse();
    }
    static public Actor createCowboy() {
        return new Cowboy();
    }
    static public Actor createCowgirl() {
        return new Cowgirl();
    }
}

And we can alter our StampedeSimulator to look like this:


public class StampedeSimulator {
    Actor actor = null;

    public void addActor(String type) {
        if (type.equals("buffalo")) {
            actor = ActorFactory.createBuffalo();
        } else if (type.equals("horse")) {
            actor = ActorFactory.createHorse();
        } else if (type.equals("cowboy")) {
            actor = ActorFactory.createCowboy();
        } else if (type.equals("cowgirl")) {
            actor = ActorFactory.createCowgirl();
        }

        // rest of stampede simulator here
    }
}

Only this is a little unsatisfying, because we now we have two if-then-else clauses! So let's parameterize the factory to take a string indicating what kind of object to instantiate:


public class ActorFactory {
    static public Actor createActor(String type) {
        if (type.equals("buffalo")) {
            return new Buffalo();
        } else if (type.equals("horse")) {
            return new Horse();
        } else if (type.equals("cowboy")) {
            return new Cowboy();
        } else if (type.equals("cowgirl")) {
            return new Cowgirl();
        } else {
            return null;
        }
    }
}

public class StampedeSimulator {
    Actor actor = null;

    public void addActor(String type) {

        actor = ActorFactory.createActor(type);

        // rest of stampede simulator here
    }
}

There we go; now we have a nice separation between the instantiation of the concrete classes and our main code. Notice that the return type of the method in the factory is an interface (or it can be an abstract class), which enforces the fact that your client doesn't have to know about the concrete classes. So, by writing your client code to use the interface, you keep it decoupled from your concrete classes. The Static Factory takes care of creating the objects you want, and your client code doesn't have to worry about it. Now, if you need to make changes, you go to one place in the code where those instantiations are "encapsulated."

So this encapsulation of our concrete classes into the factory is a good thing--we've decoupled the main code from the concrete classes--but the factory itself still depends on concrete classes, and if we need to change those classes in the factory, it means going into the code, making the changes, and recompiling. So we're not where we want to be yet, but we'll get there by removing all such dependencies in our code.

Before we go on, we should point out that the Static Factory is usually considered an idiom rather than a true design pattern, but it is in such common use that people usually use the word "factory" to apply to this method of creating objects. In any case, you can use the techniques we're about to go over with Static Factory or any of the true factory patterns (like the Factory Method or Abstract Factory patterns).

Let's Break that Last Dependency

We've decoupled the main portion of the application from the concrete classes, but the Static Factory, ActorFactory, is still tightly bound to each concrete class. In addition, that's a pretty ugly if-then-else statement inside the factory. How can we improve this and remove these last dependencies?

One technique is to use Java's Class.forName(). The forName() class method allows you to load a class dynamically by passing it a string representing the package and name of the class. Once you've got the class, you just need to instantiate a new instance of it, and return it. Let's see how this works:


class ActorFactory {
    static public Actor createActor(String type) {
    	Actor actor = null;
        Class actorClass = null;

        try {
            actorClass = Class.forName(type);
        } catch (ClassNotFoundException e) {
            System.out.println("Error: class " + type + " not found.");
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }

        if (actorClass != null) {
            try {
                actor = (Actor) actorClass.newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return actor;
    }
}

This code further decouples your application from the concrete classes, because now you can pass in any class name (or at least, any class that implements the Actor interface) to the factory, and you'll get an instance of that class. The price we pay for this decoupling is that we have to do checks all along the way: first, to make sure that the string we pass in representing the class actually exists, and then to make sure that you can actually make an instance of that class. We're a bit lazy here, and we just print out a stacktrace of the exception that occurs if we can't load the class or instantiate an object; in a real-world application, obviously you'd have to do more. We're also trading the flexibility for less control over static type checking (but good testing tends to make this a non-issue). You should think through the subtleties though; for instance, it would be perfectly legal for us to load the Actor class, but we can't actually instantiate an object from the Actor class, because Actor is an interface.

Once we've made this change to ActorFactory, we just have to make a small change to the application code that passes the string representing the type of actor we want. For example:


    simulator.addActor("headfirst.factory.simulator.Buffalo");
    simulator.addActor("headfirst.factory.simulator.Horse");
    simulator.addActor("headfirst.factory.simulator.Cowboy");
    simulator.addActor("headfirst.factory.simulator.Cowgirl");

That's it! We can compile and run this code and we get exactly the same result as before: one of each type of actor is instantiated.

Now, when we want to change the actors for the stampede simulator (for instance, when we're making a movie with animated actors instead of real actors), all we have to do is change the string representing the type of actor we pass to addActor() (which in turn gets passed to the ActorFactory). We don't have to change any code in the ActorFactory or StampedeSimulator classes at all.

Taking It All the Way

This is an improvement, but the code is still coupled to the specific types of actors: we still have to specify the name of the Actor type we want in our code and pass it to addActor(), which means we have to recompile when we want to change actors. Is there any way to get the actor types out of there altogether, so there's no code that depends on the type of the actor we want?

One way we can remove all code that depends on a specific type is to specify the types of actors we want in a properties file and load them at runtime. Then we'll have no code that depends on the type of the actor.

To do this, we change how we specify the actor type. Instead of hardcoding the actor type, we'll replace this with code to load the types from a properties file called actor.properties. The properties file has one line for each actor type you need, and looks like this:


buffalo = headfirst.factory.simulator.Buffalo
horse = headfirst.factory.simulator.Horse
cowboy = headfirst.factory.simulator.Cowboy
cowgirl = headfirst.factory.simulator.Cowgirl

This is the standard format for a Java properties file: the name of the property (e.g., buffalo), then =, then the value of the property. Now, instead of passing the fully qualified pathname of the type of actor to createActor(), we just pass in a string representing the type (like we did in the first version of the code), which should match a property in the properties file:


    simulator.addActor("buffalo");
    simulator.addActor("horse");
    simulator.addActor("cowboy");
    simulator.addActor("cowgirl");

We also modify the ActorFactory method, createActor(), to load the properties from the properties file into a Properties instance. Then we use the type passed into createActor() (for instance, "buffalo") to get the value of that property--the fully qualified type of the actor--and use it to instantiate the actor object desired:


    static public Actor createActor(String type) {
        Class actorClass = null;
        Actor actor = null;
        String actorType = null;
        Properties properties = new Properties();

        try {
            properties.load(new FileInputStream("simulator.properties"));
        } catch (IOException e) {
            System.out.println("Error: couldn't read from the simulator.properties file."
                    + e.getMessage());
        }
        actorType = properties.getProperty(type);
        if (actorType == null || actorType.equals("")) {
            System.out.println("Error loading actor type for type: " + type);
        }

        try {
            actorClass = Class.forName(actorType);
        } catch (ClassNotFoundException e) {
            System.out.println("Error: class " + actorType + " not found!");
            System.out.println(e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
        }

        if (actorClass != null) {
            try {
                actor = (Actor) actorClass.newInstance();
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println(e.getMessage());
            }
        }
        return actor;
    }

You could, of course, add properties to specify how many of each type to add to the simulator, as well.

Now you have no need to specify an actor concrete class anywhere in your code. You're completely decoupled!

Summary

The intent of the various factory patterns is to reduce dependencies on concrete classes. Let's step through our progress and see how we removed the last dependency. First, we pulled the code to instantiate objects out of our main code and put it into a factory class. Then, we improved on this by loading the concrete classes and instantiating them dynamically, based on the path and class name passed to the factory. Just make sure each class passed in implements the interface returned by the factory. Last, we broke the final dependency by loading the types we want to use in the simulator from the properties file. This eliminated dependencies to concrete classes in our code altogether.

Remember, when you reduce dependencies, you make your code more flexible and easier to maintain and extend.

Complete Code

If you want to try this program, you can copy the code below to one file, StampedeSimulatorTestDrive.java:


package headfirst.factory.simulator;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

public class StampedeSimulatorTestDrive {
 
    public static void main(String[] args) {
        System.out.println("Stampede Test Drive");
        StampedeSimulator simulator = new StampedeSimulator();

        simulator.addActor("buffalo");
        simulator.addActor("horse");
        simulator.addActor("cowboy");
        simulator.addActor("cowgirl");
    }
}

class StampedeSimulator {

    public void addActor(String type) {
        Actor actor = null;

        actor = ActorFactory.createActor(type);
        actor.display();

        // rest of stampede simulator here
    }
}

class ActorFactory {

    static public Actor createActor(String type) {
        Class actorClass = null;
        Actor actor = null;
        String actorType = null;
        Properties properties = new Properties();

        try {
            properties.load(new FileInputStream("simulator.properties"));
        } catch (IOException e) {
            System.out.println("Error: couldn't read from the simulator.properties file."
                                + e.getMessage());
        }

        actorType = properties.getProperty(type);
        if (actorType == null || actorType.equals("")) {
            System.out.println("Error loading actor type for type: " + type);
        }

        try {
            actorClass = Class.forName(actorType);
        } catch (ClassNotFoundException e) {
            System.out.println("Error: class " + actorType + " not found!");
            System.out.println(e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
        }

        if (actorClass != null) {
            try {
                actor = (Actor) actorClass.newInstance();
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println(e.getMessage());
            }
        }
        return actor;
    }
}

interface Actor { 
    public void display();
}

class Buffalo implements Actor { 
    public void display() {
        System.out.println("I'm a Buffalo");
    }
}

class Horse implements Actor { 
    public void display() {
        System.out.println("I'm a Horse");
    }
}

class Cowboy implements Actor { 
    public void display() {
        System.out.println("I'm a Cowboy");
    }
}

class Cowgirl implements Actor { 
    public void display() {
        System.out.println("I'm a Cowgirl");
    }
}

Make sure and save this file in the directory src/headfirst/factory/simulator. (If you've already downloaded and run the code from Head First Design Patterns, you should already have a src/headfirst/factory directory. Just create a new directory, simulator/, in the factory/ directory.) Create a directory classes/ to store your class files.

Don't forget to create a file, simulator.properties, containing your properties (this file should be at the top level):


buffalo = headfirst.factory.simulator.Buffalo
horse = headfirst.factory.simulator.Horse
cowboy = headfirst.factory.simulator.Cowboy
cowgirl = headfirst.factory.simulator.Cowgirl

Then, compile and run as follows:


javac -d ./classes ./src/headfirst/factory/simulator/StampedeSimulatorTestDrive.java
java -cp ./classes headfirst.factory.simulator.StampedeSimulatorTestDrive

You should see the following output:


Stampede Test Drive
I'm a Buffalo
I'm a Horse
I'm a Cowboy
I'm a Cowgirl

Elisabeth Freeman is an author, teacher and Java software developer.

Eric Freeman is enjoying taking all the pixie dust from his four years as a Disney executive and applying it to his passion, computer science.

View all java.net Articles.

 Feed java.net RSS Feeds