Bridge

Decouple an abstraction from itsimplementation so that the two canvary independently.

Introduction

Bridge Pattern is one of the Patterns in the Structural Pattern group.

Its idea is to separate abstraction from its realism. From there, it can be easily edited or replaced without affecting the places where the original layer was used. Use Bridge Pattern when we want:

  • When you want to separate the binding between Abstraction and Implementation, so that they can be easily extended independently.

  • Both their Abstraction and Implementation should be extended with subclasses.

  • Use in places where changes made in the implementation do not affect the client side.

Purpose is born

I have 3 examples from simple to complex to illustrate the problem when there is no Bridge Pattern:

VD1

Imagine that you have a game. The game's character has two attributes: clan and profession. Each clan and occupation will have different playing mechanisms in terms of skills, items and many other factors. When there are more clans and professions, the number of subclasses that manage the logic for the player's choices will increase greatly.

VD2

In a company there are many types of employees. Each type of employee has a different salary calculation method. Your employee management program will calculate the salary for an employee how they move from one category to another (or move from one department to another,). For example: When you apply for a developer position at a software company, when you start, you are a junior, a higher level is a senior, and even higher is a leader role. Each time you "level up" like that is a change to the salary calculation formula.

VD3

Suppose you have a Shape class and 2 subclasses, Circle and Square. Then, due to the need arising, you want to combine additional colors, Red and Blue. However, you already have two subclasses, so if you want to add colors, you have to create 4 subclasses: Blue Square, Red Square, Green Circle, Red Circle... If we add another color or shape, we will have to Create additional inherited classes.

=> Problem: Adding new shapes and colors to the system will require creating more inheritance classes.

This problem occurs when we try to extend Shape and Color in two independent dimensions, a very common problem for Inheritance in object-oriented programming.

From there, the Bridge pattern was born, which helps convert from inheritance to composition.

Solution: In the Shape class there is an attribute called Color, Color can add inherited colors such as Blue Red Purple Yellow as desired. Then, if we want a Red Rectangle, we only need a Rectangle with the Color attribute red, similar to other shapes without having to inherit much.

Abstraction and Implementation

The GoF book introduces the terms Abstraction and Implementation as part of the Bridge definition. In my opinion, the terms sound too academic and make the pattern seem more complicated than it really is. Having read the simple example with shapes and colors, let’s decipher the meaning behind the GoF book’s scary words.

Abstraction (also called interface) is a high-level control layer for some entity. This layer isn’t supposed to do any real work on its own. It should delegate the work to the implementation layer (also called platform).

Note that we’re not talking about interfaces or abstract classes from your programming language. These aren’t the same things.

When talking about real applications, the abstraction can be represented by a graphical user interface (GUI), and the implementation could be the underlying operating system code (API) which the GUI layer calls in response to user interactions.

Generally speaking, you can extend such an app in two independent directions:

  • Have several different GUIs (for instance, tailored for regular customers or admins).

  • Support several different APIs (for example, to be able to launch the app under Windows, Linux, and macOS).

In a worst-case scenario, this app might look like a giant spaghetti bowl, where hundreds of conditionals connect different types of GUI with various APIs all over the code.

You can bring order to this chaos by extracting the code related to specific interface-platform combinations into separate classes. However, soon you’ll discover that there are lots of these classes. The class hierarchy will grow exponentially because adding a new GUI or supporting a different API would require creating more and more classes.

Let’s try to solve this issue with the Bridge pattern. It suggests that we divide the classes into two hierarchies:

  • Abstraction: the GUI layer of the app.

  • Implementation: the operating systems’ APIs.

The abstraction object controls the appearance of the app, delegating the actual work to the linked implementation object. Different implementations are interchangeable as long as they follow a common interface, enabling the same GUI to work under Windows and Linux.

As a result, you can change the GUI classes without touching the API-related classes. Moreover, adding support for another operating system only requires creating a subclass in the implementation hierarchy.

Structure

Components in the model:

  • Abstraction (Shape): defines the interface of the abstract class, manages references to concrete implementation objects (Implementation).

  • Refined Abstraction (Circle, Square): kế thừa Abstraction.

  • Implementation (Color): defines the interface for implementation classes. Usually it is an interface that defines certain tasks of the Abstraction.

  • ConcreteImplementation (Red, Blue): inherits Implementation and defines detailed implementation functions.

Advantages & disadvantages

Advantages

  • Reduce dependency between abstraction and implementation (loose coupling): inheritance in OOP often ties abstraction and implementation when building the program. The Bridge Pattern can be used to break this dependency and allow us to choose the appropriate implementation at runtime.

  • Reduce the number of unnecessary subclasses: some cases using inheritance will greatly increase the number of subclasses. For example, in the case of a program that views images on different operating systems, we have 6 image types (JPG, PNG, GIF, BMP, JPEG, TIFF) and 3 operating systems (Window, MacOS, Ubuntu). Using inheritance in this case will make us design 18 classes: JpgWindow, PngWindow, GifWindow, .... While applying Bridge will reduce the number of classes to 9 classes: 6 classes corresponding to each implementation of Image and 3 classes corresponding to each operating system, each operating system will include a reference to a specific Image object.

  • The code will be cleaner and the application size will be smaller: due to reducing the number of unnecessary classes.

  • Easier to maintain: its Abstractions and Implementations will be easy to change at runtime as well as when additional changes are needed in the future.

  • Easy to expand later: usually large applications often require us to add modules to existing applications but not modify existing frameworks/applications because those frameworks/applications can be upgraded by the company. upgrade to new version. Bridge Pattern will help us in this case.

  • Allows implementation details to be hidden from the client: since abstraction and implementation are completely independent, we can change a component without affecting the client side. For example, the image viewer classes will be independent of the image drawing algorithms in the implementations. So we can update the image viewer program when there is a new image drawing algorithm without having to make many modifications.

Disadvantages

  • Can increase complexity when applied to a highly cohesive class

When to use it

Bridge is used when:

  • When you want to separate the binding between Abstraction and Implementation, so that you can easily extend them independently.

  • As both their Abstraction and Implementation should be extended by subclass.

  • Changes in the added components of an Abstraction without impact on Clients

Applicability

  • Use the Bridge pattern when you want to divide and organize a monolithic class that has several variants of some functionality (for example, if the class can work with various database servers).

    • The bigger a class becomes, the harder it is to figure out how it works, and the longer it takes to make a change. The changes made to one of the variations of functionality may require making changes across the whole class, which often results in making errors or not addressing some critical side effects.

    • The Bridge pattern lets you split the monolithic class into several class hierarchies. After this, you can change the classes in each hierarchy independently of the classes in the others. This approach simplifies code maintenance and minimizes the risk of breaking existing code.

  • Use the pattern when you need to extend a class in several orthogonal (independent) dimensions.

    • The Bridge suggests that you extract a separate class hierarchy for each of the dimensions. The original class delegates the related work to the objects belonging to those hierarchies instead of doing everything on its own.

  • Use the Bridge if you need to be able to switch implementations at runtime.

    • Although it’s optional, the Bridge pattern lets you replace the implementation object inside the abstraction. It’s as easy as assigning a new value to a field.

By the way, this last item is the main reason why so many people confuse the Bridge with the Strategy pattern. Remember that a pattern is more than just a certain way to structure your classes. It may also communicate intent and a problem being addressed.

How to Implement

  1. Identify the orthogonal dimensions in your classes. These independent concepts could be: abstraction/platform, domain/infrastructure, front-end/back-end, or interface/implementation.

  2. See what operations the client needs and define them in the base abstraction class.

  3. Determine the operations available on all platforms. Declare the ones that the abstraction needs in the general implementation interface.

  4. For all platforms in your domain create concrete implementation classes, but make sure they all follow the implementation interface.

  5. Inside the abstraction class, add a reference field for the implementation type. The abstraction delegates most of the work to the implementation object that’s referenced in that field.

  6. If you have several variants of high-level logic, create refined abstractions for each variant by extending the base abstraction class.

  7. The client code should pass an implementation object to the abstraction’s constructor to associate one with the other. After that, the client can forget about the implementation and work only with the abstraction object.

Relations with Other Patterns

  • Bridge is usually designed up-front, letting you develop parts of an application independently of each other. On the other hand, Adapter is commonly used with an existing app to make some otherwise-incompatible classes work together nicely.

  • Bridge, State, Strategy (and to some degree Adapter) have very similar structures. Indeed, all of these patterns are based on composition, which is delegating work to other objects. However, they all solve different problems. A pattern isn’t just a recipe for structuring your code in a specific way. It can also communicate to other developers the problem the pattern solves.

  • You can use Abstract Factory along with Bridge. This pairing is useful when some abstractions defined by Bridge can only work with specific implementations. In this case, Abstract Factory can encapsulate these relations and hide the complexity from the client code.

  • You can combine Builder with Bridge: the director class plays the role of the abstraction, while different builders act as implementations.

Last updated