This chapter covers the bridge pattern.
GoF Definition
Decouple an abstraction from its implementation so that the two can vary independently.
Concept
This pattern is also known as the handle/body pattern , in which you separate an implementation from its abstraction and build separate inheritance structures for them. Finally, you connect them through a bridge.
You must note that the abstraction and the implementation can be represented either through an interface or an abstract class, but the abstraction contains a reference to its implementer. Normally, a child of an abstraction is called a refined abstraction and a child of an implementation is called a concrete implementation .
This bridge interface makes the functionality of concrete classes independent from the interface implementer classes. You can alter different kinds of classes structurally without affecting each other.
Real-World Example
In a software product development company, the development team and the marketing team both play a crucial role. Marketing teams do market surveys and gather customers’ needs, which may vary depending on the nature of the customers. Development teams implement those requirements in their products to fulfill the customers’ needs. Any change (e.g., in the operational strategy) in one team should not have a direct impact on the other team. Also, when new requirements come from the customer side, it should not change the way that developers work in their organization. In a software organization, the marketing team plays the role of the bridge between the clients and the development team.
Computer-World Example
GUI frameworks can use the bridge pattern to separate abstractions from platform-specific implementation. For example, using this pattern, it can separate a window abstraction from a window implementation for Linux or macOS.
Note
In Java, you may notice the use of JDBC, which provides a bridge between your application and a particular database. For example, the java.sql.DriverManager class and the java.sql.Driver interface can form a bridge pattern where the first one plays the role of abstraction and the second one plays the role of implementor. The concrete implementors are com.mysql.jdbc.Driver or oracle.jdbc.driver.OracleDriver, and so forth.
Illustration
Suppose that you are a remote-control maker and you need to make remote controls for different electronic items. For simplicity, let’s assume that you are presently getting orders to make remote controls for televisions and DVD players. Let’s also assume that your remote control has two major functionalities: on and off.
On further analysis, you discover that Approach 1 is truly messy and difficult to maintain.
At first, Approach 2 looks cleaner, but if you want to include new states, such as sleep, mute, and so forth, or if you want to include new electronic items, such as AC, DVD, and so on, you face new challenges because the elements are tightly coupled in this design approach. But in a real-world scenario, this kind of enhancement is often required.
This is why, you need to start with a loosely coupled system for future enhancements so that either of the two hierarchies (electronics items and their states) can grow independently. The bridge pattern perfectly fits this scenario.
Abstraction (an abstract class) defines the abstract interface and it maintains the Implementor reference.
RefinedAbstraction (a concrete class) extends the interface defined by Abstraction.
Implementor (an interface) defines the interface for implementation classes.
ConcreteImplementor (Concrete class) implements the Implementor interface.
I followed a similar architecture in the following implementation. For your ready reference, I have pointed out all the participants in the following implementation with comments.
Class Diagram
Package Explorer View
Key Characteristics
The ElectronicGoods abstract class plays the role of abstraction. The State interface plays the role of the implementor.
The concrete implementors are OnState class and OffState class. They have implemented the moveState() and hardPressed()interface methods as per their requirements.
The ElectronicGoods abstract class holds a reference of the State implementor.
The abstraction methods are delegating the implementation to the implementor object. For example, notice that hardButtonPressed() is actually shorthand for state.hardPressed(), where state is the implementor object.
There are two refined abstractions: Television and DVD. The class is happy with the methods that it inherits from its parent. But the DVD class wants to provide an additional feature, so it implements a DVD-specific method: doublePress(). The doublePress() method is coded in terms of superclass abstraction only.
Implementation
Output
Q&A Session
- 1.
This pattern looks similar to a state pattern. Is this correct?
No. The state pattern falls into the behavioral pattern and its intent is different. In this chapter, you have seen an example where the electronic items can be in different states, but the key intent was to show thatHow you can avoid tight coupling between the items and their states.
How you can maintain two different hierarchies and both of them can extend without making an impact to each other.
In addition to these points, you are dealing with multiple objects in which implementations are shared among themselves.
- 2.
You could use simple subclassing instead of this kind of design. Is this correct?
No. With simple subclassing, your implementations cannot vary dynamically. It may appear that the implementations behave differently with subclassing techniques, but actually, those kinds of variations are already bound to the abstraction at compile time.
- 3.
In this example, I see lots of dead code. Why are you keeping those?
Some developers prefer constructors over Getter/Setter methods. You can see the variations in different implementations. I am keeping those for your ready reference. You are free to use any of them.
- 4.What are key advantages of using a bridge design pattern?
The implementations are not bound to the abstractions.
Both the abstractions and the implementations can grow independently.
Concrete classes are independent from the interface implementer classes (i.e., changes in one of these does not affect the other). You can also vary the interface and the concrete implementations in different ways.
- 5.What are the challenges associated with this pattern?
The overall structure may become complex.
Sometimes it is confused with the adapter pattern. (The key purpose of an adapter pattern is to deal with incompatible interfaces only.)
- 6.
Suppose I have only one state; for example, either OnState or OffState. In this case, do I need to use the State interface?
No, it is not mandatory. GoF classified this case as a degenerate case of the bridge pattern.
- 7.
In this example, an abstract class is used to represent an abstraction and an interface is used for an implementation. Is it mandatory?
No. You can also use an interface for abstraction. Basically, you can use either of an abstract class or an interface for any of the abstractions or implementations. I simply used this format for better readability.