Design Pattern Bridge, in a nutshell

Luiz Gustavo De O. Costa
4 min readFeb 17, 2021

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.

Forth Railway Bridge, Scotland
  • 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.

GOF Builder design

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.

Hands-on model

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.

Vehicle

As mentioned before, any class inherited from Vehicle represents the Refined Abstraction.

Aircraft
Train
Truck
WaterCraft

Below, it’s time to know the Implementor and Concrete Implementors

The first one is the Container interface, ie, the Implementor.

Container

And then the Concrete Implementors

DryBulk
HighCube
Refrigerated

The enum below represents each container dimension, real data 🚚 ✈️ 🚋 🚢.

Just to remember enum is a Singleton 😉

TypeDimension

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

Vehicle test

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.

WaterCraft test

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.

Train test

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.

TypeDimension 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

4 — Code — https://github.com/luizgustavocosta/design-patterns-in-java/tree/master/gang-of-four/src/main/java/com/gof/structural/bridge

--

--

Luiz Gustavo De O. Costa

Hey friend!! I’m Luiz Gustavo, a Java developer and I’m here to learn and write about Java, tests and good practices