Using Abstractions and the Bridge Pattern in Java
- A Classic Example of Bridge: Drivers
- Refactoring to Bridge
- A Bridge Using the List Interface
- Summary
Bridge
The Bridge pattern focuses on the implementation of an abstraction. Design Patterns (Gamma et al. 1995) uses the word abstraction to refer to a class that relies on a set of abstract operations, where several implementations of the set of abstract operations are possible.
The ordinary way to implement an abstraction is to create a class hierarchy, with an abstract class at the top that defines the abstract operations; each subclass in the hierarchy provides a different implementation of the set of abstract operations. This approach becomes insufficient when you need to subclass the hierarchy for another reason. It can also happen that the abstract operations need to be defined and implemented in advance of abstractions that will use them.
You can create a bridge by moving the set of abstract operations to an interface, so that an abstraction will depend on an implementation of the interface. The intent of the Bridge pattern is to decouple an abstraction from the implementation of its abstract operations, so that the abstraction and its implementation can vary independently.
A Classic Example of Bridge: Drivers
A common use of Bridge occurs when an application uses a driver. A driver is an object that operates a computer system or an external device according to a well-specified interface. Applications that use drivers are abstractions: The effect of running the application depends on which driver is in place. Each driver is an instance of the Adapter pattern, providing the interface a client expects, using the services of a class with a different interface. An overall design that uses drivers is an instance of Bridge. The design separates application development from the development of the drivers that implement the abstract operations on which the applications rely.
An everyday example of applications using drivers to operate computer systems appears in database access. Database connectivity in Java usually depends on JDBC. ("JDBC" is a trademarked name, not an acronym.) A good resource that explains how to apply JDBC is JDBC Database Access with Java™ (Hamilton, Cattell, and Fisher 1997). In short, JDBC is an application programming interface (API) for executing structured query language (SQL) statements. Classes that implement the interface are JDBC drivers, and applications that rely on these drivers are abstractions that can work with any database for which a JDBC driver exists. The JDBC architecture decouples an abstraction from its implementation so that the two can vary independentlyan excellent example of Bridge.
To use a JDBC driver, you load it, connect to a database, and create a Statement object:
Class.forName(driverName); Connection c = DriverManager.getConnection(url, user, passwd); Statement s = c.createStatement();
A discussion of how the DriverManager class works is outside the scope of a discussion on Bridge. The point here is that the variable s is a Statement object, capable of issuing SQL queries that return result sets:
ResultSet r = s.executeQuery( "select name, apogee from firework"); while(r.next()) { String name = r.getString("name"); int apogee = r.getInt("apogee"); System.out.println(name + ", " + apogee); }
Challenge 6.1
Figure 6.1 shows a UML sequence diagram that illustrates the message flow in a typical JDBC application. Fill in the missing type names and the missing message name in this illustration.
A solution appears on page 371.
Figure 6.1 is a sequence diagram. You can use a class diagram to show the relationship of the application to the driver it applies. Figure 6.2 shows the general form of an abstraction and its implementation in an application of the Bridge pattern. This figure brings out the separation of an abstraction and its implementation. The effect of calling the operation() method depends on which implementation of the Implementor interface is in place.
Figure 6.1: This diagram shows most of the typical message flow in a JDBC application.
Figure 6.2: A Bridge structure moves the abstract operations that an abstraction relies on into a separate interface.
It can be challenging to see how the classes in a JDBC application align with the Bridge structure that Figure 6.2 shows. The questions to answer are: Which classes are abstractions? Which classes are "implementations"?
Consider the _JDBCAdapter class that Sun Microsystems packages in the JDK as a demonstration of how to display data from a database table. This class implements the TableModel interface as shown in Figure 6.3.
Figure 6.3: The JDBCAdapter class adapts information in a database table to appear in Swing Jtable component.
The JDBCAdapter class constructor establishes a database connection and creates a Statement object. The adapter passes queries to the Statement object and makes the results available in a GUI component.
The JDBCAdapter class assumes that a client will call executeQuery() before making calls to extract data from the query. The results of not providing a query are somewhat unpredictable. Noticing this, suppose that you decide to subclass JDBCAdapter with an OzJDBCAdapter class that throws a NoQuery exception if a client forgets to issue a query before asking for data.
Challenge 6.2
Draw a diagram that shows the relationships among the classes _JDBCAdapter, OzJDBCAdapter, NoQuery, and Statement.
A solution appears on page 371.
The JDBC architecture clearly divides the roles of the driver writer and the application writer. In some cases, this division will not exist in advance, even if you are using drivers. You may be able to set up drivers as subclasses of an abstract superclass, with each subclass driving a different subsystem. In such a case, you can be forced to set up a Bridge when you need more flexibility.