Bridge
Bridge decouples an abstraction from its implementation via composition so both vary independently, avoiding subclass explosion across two dimensions. It adds upfront design complexity.
The Bridge pattern decouples an abstraction from its implementation so that the two can evolve independently. It addresses a structural problem that appears when a type varies along two independent dimensions at once: combining them through inheritance produces an explosion of subclasses (M abstractions times N implementations equals M times N classes). Bridge replaces that with composition, turning the multiplication into an addition.
How It Works
The design splits the hierarchy in two. The Abstraction defines the high-level interface and holds a reference to an Implementor. The Implementor is a separate interface defining primitive operations; Concrete Implementors provide them. Refined Abstractions extend the abstraction with richer behavior built on the implementor's primitives.
Client code talks to the abstraction, which forwards low-level work to its implementor. Because the abstraction holds the implementor by reference, the implementation can be chosen — and even swapped — at runtime. Adding a new abstraction or a new implementation means adding one class on one side, not one per combination.
When to Use It
Use Bridge when a class varies along two or more orthogonal dimensions, when you want to switch implementations at runtime, when you want to share an implementation among several abstractions, or when both abstraction and implementation should be extensible by subclassing independently. Classic uses include cross-platform rendering (shapes versus drawing backends), device drivers, and persistence abstractions over multiple stores.
It is unnecessary when there is only one implementation or the dimensions are not truly independent — then the extra indirection adds complexity for nothing.
Trade-offs
Bridge prevents subclass explosion, lets abstraction and implementation change without affecting each other, hides implementation details from clients, and supports runtime implementation switching. It improves the open/closed compliance of both hierarchies.
The drawback is upfront design complexity: you must identify the two dimensions correctly and introduce two parallel hierarchies, which can feel heavy and is harder for newcomers to grasp than straightforward inheritance. Applied where dimensions are not genuinely orthogonal, it adds indirection without benefit and can obscure the code.
Related Patterns
Bridge is structurally similar to Adapter but differs in intent and timing: Adapter retrofits two existing, incompatible interfaces, whereas Bridge is designed up front to keep two hierarchies separate. It also relates to Strategy and Abstract Factory — Strategy swaps an algorithm via composition much as Bridge swaps an implementation, and an Abstract Factory can supply the right implementor for an abstraction.
Example
A notification system has message types (Alert, Reminder) and delivery channels (Email, SMS, Push). With inheritance you would need a class per combination. With Bridge, Notification (the abstraction) holds a Channel (the implementor). AlertNotification and ReminderNotification extend the abstraction; EmailChannel, SmsChannel, and PushChannel implement the channel. Adding a Slack channel is one new class usable by every notification type.