- A Classic Example of Bridge: Drivers
- Refactoring to Bridge
- A Bridge Using the List Interface
- Summary
Refactoring to Bridge
Sometimes, you have to refactor an abstract class into a Bridge to gain more flexibility in the abstraction. Consider the MachineController hierarchy at Oozinoz. Other than database drivers, the most common application of drivers is to operate external devices. These devices may be peripherals, such as printers and pen plotters, or any machine capable of communicating with computers. Applications at Oozinoz control most of the machines on the factory floor with the drivers, or controllers, that Figure 6.4 shows.
Figure 6.4: Instances of classes in the MachineController hierarchy drive machines in the Oozinoz factory.
The MachineController class defines the interface for an object that controls a machine. Subclasses provide different implementations of the methods that drive a machine. Most of the methods that Machine-Controller declares are abstract, but it does have concrete methods that depend on its abstract methods. For example, the inputFull() method is concrete, but its effects depend on how subclasses implement getQueueMax():
public boolean inputFull() { return getQueueMax() >= getQueue().size(); }
The MachineController class is an abstraction whose implementation occurs in its subclasses, and we can consider separating the abstraction from its implementation. The motivation to separate an abstraction from its implementation usually occurs when a new reason for subclassing emerges. For example, suppose that you want to differentiate normal controllers from testing controllers. A testing controller has new methods that test or stress machines, such as an overflow() method that dispatches material to a machine until the input queue overflows and the machines signals an alarm. Figure 6.5 shows a design that provides for testing classes.
Figure 6.5: In this factoring, subclasses of MachineController exist for
various types of machines and for various types of controllers.
Introducing a TestController class turns out to force you to create three new classes, as test controllers for star presses and shell assemblers implement getQueueMax() differently. This is a problem because you'd like to be able to add new classes individually. The problem arises because the hierarchy is factored according to two principles: Each machine needs its own controller, and different types of controllers are used for normal operation and for testing. You really need a class for every combination of machine type and controller typea maintenance nightmare. You can solve the problem by separating the MachineController abstraction from its implementation.
To refactor a hierarchy with an abstract class at its top into a bridge, do the following.
Move the abstract operations in the superclass into an interface.
Define implementation classes that provide different implementations of the interface.
Redefine the remaining operations in the abstract class as operations on an instance of the new interface.
Challenge 6.3
Figure 6.6 shows the MachineController hierarchy refactored into a bridge. Fill in the missing labels.
Figure 6.6: This diagram, when complete, will show the results of refactoring MachineController to apply Bridge.
A solution appears on page 373.
After this type of refactoring, you can usually change the declaration of the abstract superclass to be a concrete class. Note, however, that the class is still an abstraction in that its operations depend on the implementation of abstract operations defined in the interface.