Bridge Pattern
Ever felt overwhelmed by the explosion of subclasses when trying to support multiple variations in your code?
What if you could mix and match different functionalities in your software without creating a tangled mess of code?
Incomes the Bridge Pattern
THE WHAT
The Bridge pattern is a structural design pattern that allows you to split a large class or a set of closely related classes into two separate hierarchies: abstraction and implementation. These two hierarchies can be developed independently, making the code more manageable and extensible.
The General Problem It Tries to Solve
The main problem the Bridge pattern addresses is the complexity that arises when extending a class in multiple dimensions. This situation often occurs in large systems where classes need to support multiple variants of functionality, such as different shapes and colors, or various graphical user interfaces (GUIs) and operating systems (APIs).
Example of the General Problem
Consider a scenario where we need to create various types of documents (like Reports and Letters) in different formats (like PDF and Word). If we try to use inheritance to cover all combinations, we end up with classes like PDFReport, WordReport, PDFLetter, WordLetter, and so on. This approach quickly becomes unmanageable as the number of combinations increases.
Generalizing the When
Use the Bridge pattern when you need to extend a class in multiple independent dimensions. This pattern is particularly useful when you want to divide and organize a monolithic class that has several variants of some functionality. It’s also beneficial when you need to switch implementations at runtime.
THE HOW
Here’s how you can implement the Bridge pattern using the example of documents and formats in Java:
Step 1: Identify the orthogonal dimensions in your classes.
In this case, the dimensions are Document (abstraction) and Format (implementation).
Step 2: Define the operations the client needs in the base abstraction class.
// Abstraction
abstract class Document {
protected Format format;
public Document(Format format) {
this.format = format;
}
abstract void create();
}
Step 3: Determine the operations available on all platforms. This involves defining the methods that the implementation classes must provide, regardless of their specific details.
// Implementation
interface Format {
void generate(String content);
}
Step 4: Create concrete implementation classes for all platforms.
// Concrete Implementations
class PDFFormat implements Format {
@Override
public void generate(String content) {
System.out.println("Generating PDF with content: " + content);
}
}
class WordFormat implements Format {
@Override
public void generate(String content) {
System.out.println("Generating Word document with content: " + content);
}
}
Step 5: Add a reference field for the implementation type inside the abstraction class.
// Refined Abstraction
class Report extends Document {
public Report(Format format) {
super(format);
}
@Override
void create() {
System.out.print("Creating Report. ");
format.generate("Report Content");
}
}
class Letter extends Document {
public Letter(Format format) {
super(format);
}
@Override
void create() {
System.out.print("Creating Letter. ");
format.generate("Letter Content");
}
}
Step 6: Pass an implementation object to the abstraction’s constructor to associate one with the other.
// Client
public class BridgePatternExample {
public static void main(String[] args) {
Document pdfReport = new Report(new PDFFormat());
pdfReport.create();
Document wordLetter = new Letter(new WordFormat());
wordLetter.create();
}
}
Pros:
Cons:
Thank you