Design Pattern Bridge, in a nutshell
When do you need to connect things, one of the options is to build a bridge, and this pattern is an example of it in code time upfront of integrations.
- For more about this bridge, click here.
Motivation
Excerpt from GOF book[1],
When an abstraction can have one of several possible implementations, the usual way to accommodate them is to use inheritance. An abstract class defines the interface to the abstraction, and concrete subclasses implement it in different ways. But this approach isn’t always flexible enough. Inheritance binds an implementation to the abstraction permanently, which makes it difficult to modify, extend, and reuse abstractions and implementations independently.[1]
Basically, the excerpt above says that separate the abstraction to implementation to modify both sides independently.
Where can I use this pattern?
Excerpt from Head First book[2],
Decouples an implementation so that it is not bound permanently to an interface.
Abstraction and implementation can be extended independently.
Changes to the concrete abstraction classes don’t affect the client.
Examples
GOF
The diagrams below show how to build and identify this pattern.
Hands-on
For this hands-on, I choose to connect a vehicle and a container. On the one hand, I have some kinds of vehicles and on the other hand, I’ve some containers.
Since it’s possible for a container to be transported by a ship, airplane, train, or truck I supposed that it should be a good example.
Looking to write about this pattern, I found some other examples, for instance: switch/lamps, remote control/tv, window/scroll bars. The real world is full of great examples too 😄.
Model
The model represents the problem and how the bridge pattern will help in this solution.
For sure the logic to calculate the estimated arrival time, cost, and so on is complex, but the goal of this tutorial is to give you an overview of this pattern.
Participants defined by GOF [1]
Abstraction → Defines the abstraction’s interface, maintains a reference to an object of type Implementor.[1]
Refined abstraction→ Extends the interface defined by Abstraction.[1]
Implementor → Defines the interface for implementation classes. This interface doesn’t have to correspond exactly to Abstraction’s interface; in fact, the two interfaces can be quite different. [1]
ConcreteImplementor → Implements the Implementor interface and defines its concrete implementation. [1]
Participants inside the project, and the code below
Abstraction → Vehicle
Refined abstraction→ Any class that has Vehicle as parent class.
Implementor → Container
ConcreteImplementor → Any class that implements Container.
Diagram to classes
Now the participants and the model are known, let’s see how the code is.
The first class is the Abstraction, represented by the Vehicle class.
As mentioned before, any class inherited from Vehicle represents the Refined Abstraction.
Below, it’s time to know the Implementor and Concrete Implementors
The first one is the Container interface, ie, the Implementor.
And then the Concrete Implementors
The enum below represents each container dimension, real data 🚚 ✈️ 🚋 🚢.
Just to remember enum is a Singleton 😉
Tests
For the tests, I used JUnit5, and to start, let’s test the Abstract class and the Objects.requireNonNull, since it’s required to pass implementation for the abstract.
The method catchThrowable does this job for us, capturing any exception on the method call
The test below uses one of N approaches to inject a mock, choose what is more likely for you and your problem.
In this case, I’m using MockitoExtension to achieve the Mock capability.
On the other hand and test, I’m using only the @Mock annotation and then the MockitoAnnotations.initMock(this) to achieve the same goal as the code above. Remember, to solve a problem do the easiest way 👍 for you.
Using JUnit 5 is possible to test the enum in an easy way, using the annotation @EnumSource and @ParameterizedTest. Remember, don't use the annotation @Test, since this is a parameterized test.
Code
The complete code is available on my GitHub.
Conclusion
This pattern is the opposite of the Adapter, since the adapter applies after building the software, and was really easy to understand and find a case to apply. Reading the Head First, this pattern is on a leftover part because sometimes increase the complexity, but at the same time help you to divide and take advantage using a good design and abstraction, so I liked 👍
References
1 — Gamma Erich, Helm Richard, Johnson Ralph, Vlissides John, Grady Booch. (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley.
2 — Eric Freeman, Elisabeth Robson. (2020). Head First Design Patterns, 2nd Edition. O’Reilly Media, Inc.
3 — Cover image — https://pixabay.com/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=4805387