Definition
The bridge pattern intends to decouple an abstraction from its implementation so that the two can vary independently.
The bridge pattern is a bit like the adapter pattern, but the intention of using them is different:
| Design pattern | Intention |
|---|---|
| Adapter pattern | We use it to hide the layer of calling other libraries, so that if the library changes, the error is constraint in a limited space. |
| Bridge pattern | We use it to avoid the exponential growth in number of classes. (See scenario) |
Scenario
When we have a webpage that wants to display media sources like albums and books in a large view, a medium view, or a small view, we will need $2\times3=6$ kinds of class to pair up all of them. As the views and media sources grow, the number of classes grows exponentially.
But here, we can decouple the sources from the views and combine them using the bridge pattern, so that only $2+3=5$ of classes is needed.
Class Diagram
IView defines the display methods, and it can have multiple views (ex: LargeView returns html script for displaying in large view). The view class also holds media resource IResource*, so that it can get the content.
IResource is an interface that defines a group of methods for views to call. The methods are agnostic about when they will get called and what views are calling.
The subclasses of IResource (ArtistResource and BookResource) have the actual media source (Artist and Book). Media sources (Artist and Book) are two independent classes with different methods and attributes. ArtistResource and BookResource have different media resources, but they all must have Snippet and ImageUrl defined in IResource`. This follows the segregation principle, where a class only has methods for its purpose.
The bridge pattern enables different views ($m$) to pair up with different resources ($n$) and can have $m \times n$ of combinations. And this separates the platform-specific (IView) from the platform-independent (IResource).
Used Cases
- When creating different views for different devices and different resources.
- When coupling different sets of objects.
Implementation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// bridge.h
#ifndef __BRIDGE_H__
#define __BRIDGE_H__
#include <memory>
#include "media.h" // contains Artist class and Book class
class IResource {
public:
virtual std::string Snippet() = 0;
virtual std::string ImageUrl() = 0;
};
class ArtistResource : public IResource {
private:
Artist artist_;
public:
// Another way is to pass in artist's pointer
ArtistResource(const std::string& name, const std::string& biograph) : artist_(name, biograph) {}
std::string Snippet() override;
std::string ImageUrl() override;
};
class IView {
protected:
std::unique_ptr<IResource> resource_;
public:
virtual void DisplayText() = 0;
virtual void DisplayImage() = 0;
};
class LargeView : public IView {
public:
LargeView(std::unique_ptr<IResource>&& resource);
void DisplayText() override;
void DisplayImage() override;
};
#endif //__BRIDGE_H__
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// bridge.cpp
#include "bridge.h"
#include <iostream>
std::string ArtistResource::Snippet() {
std::string snippet = std::string("Name: ") + artist_.GetName() + std::string("\n");
snippet += std::string("Bio : ") + artist_.GetBiograph() + std::string("\n");
return snippet;
}
std::string ArtistResource::ImageUrl() {
return std::string(artist_.GetMediaUrl());
}
LargeView::LargeView(std::unique_ptr<IResource>&& resource) {
resource_ = std::move(resource);
}
void LargeView::DisplayText() {
std::cout << "<p>" << resource_->Snippet() << "</p>" << std::endl;
}
void LargeView::DisplayImage() {
std::cout << "<img src='" << resource_->ImageUrl() << ">" << std::endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include "bridge.h"
int main() {
LargeView large_view = LargeView(std::make_unique<ArtistResource>("cindytsai", "born in 1997"));
large_view.DisplayText();
large_view.DisplayImage();
return 0;
}