Pattern - Bridge
Higor Diego
Posted on January 24, 2023
The Bridge pattern is one of the structural design patterns that allows the abstraction and implementation to be varied independently. It was introduced in the book "Design Patterns: Elements of Reusable Object-Oriented Software" by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides, known as "Gang of Four" (GoF) in 1994.
The Bridge pattern is one of the structural design patterns that allows you to separate the abstraction from its implementation so that both can be varied independently. It allows you to change the implementation of a class without affecting the classes that use it.
The main idea of Bridge is to create an abstract interface that defines the functionality of the class, and a concrete implementation that provides the logic for that functionality. The abstract class contains a reference to the implementing class, and the concrete classes implement that interface.
That way, when changing the implementation of a class, only the implementing class needs to be modified, classes that use the abstract interface don't need to be affected.
The Bridge pattern can be used in a variety of situations, some of which include:
- When you have an abstract class that needs to be reused, but its implementation needs to be modified frequently. Bridge allows you to change the implementation without affecting the abstract class.
- When you need to work with classes that have different implementations of the same functionality. Bridge allows you to change the implementation without affecting the classes that use the functionality.
- When you want to create an abstract class for a feature, but want to retain the flexibility to change the implementation without affecting the classes that use it.
- When you want to increase the scalability of your code, allowing you to add new implementations without affecting classes that already use the functionality.
- When you need to work with different platforms or operating systems and need to maintain the flexibility to change the implementation without affecting the classes that use it.
Below is a simple code example using the Bridge pattern.
class Abstract {
constructor(implementation) {
this.implementation = implementation;
}
execute() {
return `${this.implementation.executeImplementation()}`;
}
}
class Implementation {
executeImplementation() {
return 'execute Implementation';
}
}
class ExtendedAbstract extends Abstract {
execute() {
return `ExtendedAbstract: ${this.implementation.executeImplementation()}`;
}
}
class ConcreteImplementationA extends Implementation {
executeImplementation() {
return 'ConcreteImplementationA';
}
}
class ConcreteImplementationB extends Implementation {
executeImplementation() {
return 'ConcreteImplementationB';
}
}
const implementationA = new ConcreteImplementationA();
const AbstractA = new Abstract(implementationA);
console.log(AbstractA.execute()); // "ConcreteImplementationA"
const implementationB = new ConcreteImplementationB();
const AbstractB = new Abstract(implementationB);
console.log(AbstractB.execute()); // "ConcreteImplementationB"
const AbstractC = new ExtendedAbstract(implementationB);
console.log(AbstractC.execute()); // "ExtendedAbstract: ConcreteImplementationB"
The Abstract class is an abstract class that defines the interface for the operation. It contains a constructor that takes an instance of an implementing class and stores that reference in an instance property. It also has an execute method that uses the implementation reference to call the executeImplementation implementation method.
The Implementation class is an implementation class that defines the executeImplementation method, which returns an "execute Implementation" string.
The ExtendedAbstract class is a class that extends the Abstract class and overrides the execute method. It returns a different string, but still uses the implementation reference to call the executeImplementation implementation method.
The ConcreteImplementationA and ConcreteImplementationB classes are concrete classes that extend the Implementation class and override the executeImplementation method to return different strings.
The code creates two instances of ConcreteImplementationA and ConcreteImplementationB and passes them to the Abstract and ExtendedAbstract instances respectively. It then calls the execute method of those instances and prints the results.
This example shows how the Bridge pattern allows you to separate abstraction from implementation, so that both can be varied independently. This allows you to change the implementation without affecting the abstract class, increasing code flexibility and scalability.
Simple, right?
Imagine another scenario in which you need to search for a user's post in REST format and another search in Graphql format through different API's in the same implementation.
Follow the solution below:
const axios = require('axios')
class AbstractAPI {
constructor(implementation) {
this.implementation = implementation;
}
getData(options) {
return this.implementation.getDataImplementation(options);
}
}
class APIImplementation {
getDataImplementation() {
throw new Error('getDataImplementation method must be implemented');
}
}
class RESTAPI extends APIImplementation {
constructor(url) {
super();
this.url = url;
}
getDataImplementation(options) {
return axios.get(this.url, { params: options });
}
}
class GraphQLAPI extends APIImplementation {
constructor(url) {
super();
this.url = url;
}
getDataImplementation(options) {
return axios.post(this.url, {query: options.query, variables: options.variables});
}
}
const restAPI = new RESTAPI('https://jsonplaceholder.typicode.com/posts');
const restAPIBridge = new AbstractAPI(restAPI);
restAPIBridge.getData({ id: 1 }).then(response => console.log(response.data));
const graphQLAPI = new GraphQLAPI('https://countries.trevorblades.com/');
const graphQLBridge = new AbstractAPI(graphQLAPI);
graphQLBridge.getData({query: '{"query":"{\n country(code: \"BR\") {\n name\n }\n}"}'}).then(response => console.log(response.data));
In this example, the AbstractAPI class is the abstract class that defines the interface for the data-getting method and contains a reference to an implementation. The RESTAPI and GraphQLAPI classes are the concrete classes that extend the APIImplementation class and implement the getDataImplementation method to make different requests to their respective APIs.
RESTAPI and GraphQLAPI instances are passed to AbstractAPI instances, which are then used to make requests to the APIs, allowing you to easily switch between different implementations without changing the code that uses them.
The Bridge pattern is useful in situations where you need to decouple an abstraction from its implementation. This allows you to change the implementation without affecting the abstraction, and allows you to use many different implementations of an abstraction. Some examples of where the Bridge pattern can be useful include:
- When you want to create a library or component that can be used on different operating systems or platforms without changing the library code.
- When you want to use different database types such as MySQL, PostgreSQL or SQLite but want to keep the database access code decoupled from your business code.
- When you want to use different renderer types to draw graphics on different platforms like DirectX or OpenGL.
- When you want to use different types of APIs such as REST or GraphQL but want to keep the API consuming code decoupled from your business code.
- When you want to use different types of communication, such as serial or USB, but want to keep the communication code decoupled from your business code.
Conclusion
The Bridge pattern is a software design pattern that aims to decouple an abstraction from its implementation. It does this by creating an abstract class that defines the interface for the abstraction and an implementation class that contains the implementation logic.
Hope this helps, until next time.
Posted on January 24, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.