Design Patterns and You: The Bridge

In the spirit of the last post, I want to talk about another design pattern that’s useful in a wide range of Java development cases, and for which I think better tooling support (maybe in the form of a JDeveloper addin!) would be very useful: The bridge, also known as the driver. You’re probably familiar with the term “driver,” at least in the sense of device or JDBC drivers, and as we’ll see, there’s a reason why the driver pattern shares a name with them.

The stated purpose of this pattern is to “decouple an abstraction from its implementation so that the two can vary independently.” This may not make much sense at first, so we’ll start with a concrete example.

A Simple Bridge Example

Suppose you have an abstract class, Animal, like so:

public abstract class Animal {
    public abstract void eat(Food food);
    public abstract Object excrete();
    public abstract void maintainHomeostasis();
    public abstract void move(double x, double y, double z);
    public abstract Animal reproduce();
}

So far, so good. But of course, all of these methods need implementation in a concrete class somewhere. The logical way to do this would be to provide subclasses of Animal, such as Vertebrate, Arthropod, etc., to define subclasses of those, such as Reptile, Mammal, etc., and continue on down to the species level. Some of the intermediate classes’ methods might be abstract as well, but by the time you got to individual species, all of the methods above would have implementations, and the classes would be concrete.

This would certainly be a great plan for, say, maintainHomeostasis(), because it factors nicely up the tree. Pretty much all mammals maintain homeostasis in more or less the same way, and it’s a different way than reptiles do, so it would make sense to put the maintainHomeostatis() method direct subclasses of Vertebrate, like Reptile and Mammal.

It’s not, however, such a remarkably good plan for reproduce(), because basic features of the reproductive cycles of animals tend to cut across biological taxa. For example, both reptiles and mammals contain some egg-laying species and some non-egg-laying species, and the development patterns of, say, a truly viviparous snake and a placental mammal have a fair bit in common. There’s no good place to put that common code such that all egg-layers will share it and various non-egg-layers won’t. The hierarchy under Animal is a different hierarchy than the one we’d want for purposes of reproduction–we’d like to decouple the implementation of that method from its abstraction–that is, decouple the way reproduce() is implemented from the hierarchy of implementations under the class that declares it.

Here’s where the bridge comes in: We can actually refactor the implementation of reproduce() out into an object member. Here’s the modified Animal class–note that not all of the methods are abstract any more–indeed, reproduce() has been made final, so it can’t be implemented in specific ways by subclasses:

public abstract class Animal {
    private ReproductionManager _repMan;

    public abstract void eat(Food food);
    public abstract Object excrete();
    public abstract void move(double x, double y, double z);
    public abstract void maintainHomeostasis();

    public void setRepMan(ReproductionManager _repMan) {
        this._repMan = _repMan;
    }

    public final Animal reproduce() {
        return _repMan.reproduce();
    }
}

Of course, this requires a ReproductionManager type, but it’s really simple:

public interface ReproductionManager {
    Animal reproduce();
}
public void eat(Food food);
   public Object excrete();
   public void move(double x, double y, double z);
   public Animal reproduce();

What does this give us? Well, the real implementation of reproduce() no longer needs to be defined anywhere in Animal’s class hierarchy. Instead it can be defined in an implementation of ReproductionManager. So, say, our Monotreme class, which extends Mammal but needs to lay eggs, could be implemented like so:

public abstract class Monotreme extends Mammal {
    public Monotreme() {
        super();
        setRepMan(new OviparousReproductionManager());
    }
  // possibly some code here
}

OviparousReproductionManager, of course, is an implementation of ReproductionManager. The trick here is that you could implement, say, Viper’s constructor in exactly the same way. Similarly, you could create an implementation, ViviparousReproductionManager, that works for both Primate and AsianPipesnake.

This effectively allows for mocking up multiple inheritance in Java: Although classes can only inherit from one superclass, they can also get functionality from various bridges.

Why Is a Bridge Also Called a Driver?

The reason a bridge is also called a driver is that using a device or database driver is a perfect example of implementing the bridge pattern. Take JDBC programming. Let’s say you’re an ISV (or, say, Oracle), and want to develop a library that you will sell to multiple customers (or, say, that you will give to multiple customers with a free license if they use your application server for deployment). Your application contains class hierarchies that provides various bits of functionality–some classes might write data, some classes might read it; maybe you even have classes that specialize in particular verticals (like HR).

And now suppose you want to support multiple databases. If there were no such thing as the bridge pattern, you’d have to reimplement your entire class hierarchy for each database–since the issue of what database you’re using cuts across your functionality-based class hierarchy. That would be pretty horrible.

Fortunately, you probably coded to the JDBC interfaces in the java.sql and javax.sql packages. These interfaces are implemented by…differing JDBC drivers. The interfaces effectively form a bridge; allowing the implementation of database connectivity to vary independently of your class hierarchy. By ensuring that you parameterize driver choice, you can factor any database-specific code out of your classes*.

*Well, OK, there are some things that are going to stay database-specific, such as SQL construction. But you can factor those out into your own bridge, like ADF BC does (the interface oracle.jbo.server.SQLBuilder specifies the bridge).

Where It Gets Annoying

Implementing a bridge up front is pretty straightforward, as shown in the example above. The problem is, even with the best design intentions in the world, it may not always be obvious when you start coding if you’re going to need a bridge. For example, in the release of the Framework for Package API-Based Business Components currently on this website, I put a lot of Oracle-specific SQL construction directly in my business component framework classes. I note that I’m currently only supporting the standard Oracle database drivers, and that’s fine. The next release is only going to support the standard Oracle database drivers too, but here’s the thing: I’m hoping to put it on the Oracle sample code community website, and I’d like to make it relatively easy for other people to extend my code to support other drivers. Obviously, all my Oracle-specific code had to go into a bridge (which I’ve decided to create as a subinterface of SQLBuilder; this lets my Oracle-specific code reside in a subclass of OracleSQLBuilder), which I hadn’t thought about up-front.

Here’s what I’d have to do to get this to work in JDeveloper:

  1. Create a class to act as the Oracle implementation of my bridge.
  2. Add an object member, of the implementation type, to each of my classes that need refactoring.*
  3. On each of the methods that I want to move to the bridge, use the Refactor > Move option to move them to my implementation class.
  4. Extract a bridge interface for the bridge implementation class.
  5. Add a setter method for the object member in each of my classes.*
  6. If I had a bunch of subclasses of my classes (which I don’t in this case, but I might have), some of which override the above methods, I’d have to move those implementations out of the hierarchy as well.

*OK, actually in my specific case, I can use the framework’s built in functionality to specify a SQLBuilder implementation declaratively, but in general, I’d need to do this.

Not too bad in my particular case, but what if you needed to do this for a hierarchy with 100 classes in it already, like a large Animal hierarchy? Suppose you had egg-laying functionality in, say, your reptile class, and live-birth functionality in your mammal class, and a bunch of other stuff like this, and you needed to factor it out into an independent hierarchy? Yuck, right?

Where an IDE Could Help

So I have an idea here for a Refactor item along the lines of, “extract bridge,” which would allow you to do all of the above in a single process. It would probably open a wizard, and here’s what the wizard would do:

Page 1: Select Methods

On this page, you would be presented with a list of methods in the class you’re refactoring, which I’ll call the “source class.” Note that some or all of these methods may be abstract (that will be important later). You can select the methods you want to refactor to the bridge.

Page 2: Select Bridge Interface

On this page, you would be allowed to select an existing interface to act as your bridge, or to create a new interface (using controls very similar to the way you create new interfaces from the gallery). I’m going to call this the “bridge interface.”

Page 3 (Sometimes): Select Default Implementing Class

This page would only appear if your selected methods included some non-abstract methods. In it, you could select or create a class (which I’ll call the “default implementation”), which would be forced to implement the bridge interface, to which all the actual implementations of the concrete methods in your source class would be moved. You would also be given an option for what to do if the bridge interface contains methods beyond these (or what is already provided by the default implementation or its superclasses): Mark the default implementation as abstract, or generate stub implementations of those methods.

Page 4 (Sometimes): Select Other Implementing Classes

This is a bit more complex. The tool could search the source path for subclasses of the source class, and find any that contained their own implementations/overrides of any of the selected methods. If any of these exist, the user would be given the option of moving the code for these into other classes that implement the interface. These classes could extend either existing classes or new bridge implementations for their ancestors (or nothing at all), and again, they could be marked as abstract or as needing stub implementations.

And Then You Click Finish

When you click finish, here’s what would happen:

  1. Your bridge interface is created.
  2. Your source class is given an object member of the bridge interface type and a public setter for that member.
  3. All selected concrete members of your source class are moved to your default implementation, and their original implementations are replaced with delegations to the object member.
  4. All selected abstract members of your source class are changed to concrete delegations to the object member.
  5. Steps 3 and 4 are repeated for any subclasses you selected on Page 4 of the wizard, for declared (not inherited) members only.
  6. All implementations of the bridge interface are either marked as abstract, or appropriate stub implementations are created for them.

Now all you’d need to do is make sure the right setter methods got called at the appropriate time (in our Animal example, for instance, you’d still need to change the constructor of Monotreme, and any other classes in your hierarchy where you did want to fix a particular reproduction mode; in the JDBC case, it’s the responsibility of the DriverManager or DataSource; in the SQLBuilder case it’s handled by the application module configuration).

Next week–live from Oracle OpenWorld 2009! (More specific announcements later this week.)