Bridge Pattern
- is a type of structural design pattern that uses dependency injection to decouple an abstraction from its implementation so that the two can vary independently
Real-World Example
Weapon Enchantment Example
Consider you have a weapon with different enchantments, and you are supposed to allow mixing different weapons with different enchantments. What would you do? Create multiple copies of each of the weapons for each of the enchantments or would you just create separate enchantment and set it for the weapon as needed? Bridge pattern allows you to do the second
Code Structure
|
|
|
Code Examples
Shape Drawing Example (Simple)
Implementor Interface
interface DrawingAPI { void drawCircle(double x, double y, double radius); }Concrete Implementations
class DrawingAPI1 implements DrawingAPI { public void drawCircle(double x, double y, double radius) { System.out.println("API1 circle at (" + x + "," + y + ") radius " + radius); } } class DrawingAPI2 implements DrawingAPI { public void drawCircle(double x, double y, double radius) { System.out.println("API2 circle at (" + x + "," + y + ") radius " + radius); } }Abstraction
abstract class Shape { protected DrawingAPI drawingAPI; protected Shape(DrawingAPI drawingAPI) { this.drawingAPI = drawingAPI; } abstract void draw(); abstract void resizeByPercentage(double pct); }Refined Abstraction
class CircleShape extends Shape { private double x, y, radius; CircleShape(double x, double y, double radius, DrawingAPI api) { super(api); this.x = x; this.y = y; this.radius = radius; } public void draw() { drawingAPI.drawCircle(x, y, radius); } public void resizeByPercentage(double pct) { radius *= (1.0 + pct / 100.0); } }Usage
public class Main { public static void main(String[] args) { Shape circle1 = new CircleShape(1, 2, 3, new DrawingAPI1()); Shape circle2 = new CircleShape(5, 7, 11, new DrawingAPI2()); circle1.draw(); // uses API1 circle2.draw(); // uses API2 circle1.resizeByPercentage(50); circle1.draw(); // API1 with new radius } }
Weapon Enchantment Example
interface Enchantment { void onActivate(); void apply(); void onDeactivate(); }interface Weapon { void wield(); void swing(); void unwield(); Enchantment getEnchantment(); }class FlyingEnchantment implements Enchantment { @Override void onActivate() { log.info("The item begins to glow faintly."); } @Override void apply() { log.info("The item flies."); } @Override void onDeactivate() { log.info("The item's glow fades."); } } class SoulEatingEnchantment implements Enchantment { @Override void onActivate() { log.info("The item spreads bloodlust."); } @Override void apply() { log.info("The item eats the soul of enemies."); } @Override void onDeactivate() { log.info("Bloodlust slowly disappears."); } }class Sword implements Weapon { private final Enchantment enchantment; Sword(Enchantment enchantment) { this.enchantment = enchantment; } @Override void wield() { log.info("The sword is wielded."); enchantment.onActivate(); } @Override void swing() { log.info("The sword is swinged."); enchantment.apply(); } @Override void unwield() { log.info("The sword is unwielded."); enchantment.onDeactivate(); } @Override Enchantment getEnchantment() { return enchantment; } } class Hammer implements Weapon { private final Enchantment enchantment; Hammer(Enchantment enchantment) { this.enchantment = enchantment; } @Override void wield() { log.info("The hammer is wielded."); enchantment.onActivate(); } @Override void swing() { log.info("The hammer is swinged."); enchantment.apply(); } @Override void unwield() { log.info("The hammer is unwielded."); enchantment.onDeactivate(); } @Override Enchantment getEnchantment() { return enchantment; } }Use case
log.info("The knight receives an enchanted sword."); var enchantedSword = new Sword(new SoulEatingEnchantment()); enchantedSword.wield(); enchantedSword.swing(); enchantedSword.unwield(); log.info("The valkyrie receives an enchanted hammer."); var hammer = new Hammer(new FlyingEnchantment()); hammer.wield(); hammer.swing(); hammer.unwield();Here’s the console output.
The knight receives an enchanted sword. The sword is wielded. The item spreads bloodlust. The sword is swung. The item eats the soul of enemies. The sword is unwielded. Bloodlust slowly disappears. The valkyrie receives an enchanted hammer. The hammer is wielded. The item begins to glow faintly. The hammer is swung. The item flies. The hammer is unwielded. The item's glow fades.
Remote Controller Example
// The "abstraction" defines the interface for the "control" // part of the two class hierarchies. It maintains a reference // to an object of the "implementation" hierarchy and delegates // all of the real work to this object. class RemoteControl is protected field device: Device constructor RemoteControl(device: Device) is this.device = device method togglePower() is if (device.isEnabled()) then device.disable() else device.enable() method volumeDown() is device.setVolume(device.getVolume() - 10) method volumeUp() is device.setVolume(device.getVolume() + 10) method channelDown() is device.setChannel(device.getChannel() - 1) method channelUp() is device.setChannel(device.getChannel() + 1) // You can extend classes from the abstraction hierarchy // independently from device classes. class AdvancedRemoteControl extends RemoteControl is method mute() is device.setVolume(0) // The "implementation" interface declares methods common to all // concrete implementation classes. It doesn't have to match the // abstraction's interface. In fact, the two interfaces can be // entirely different. Typically the implementation interface // provides only primitive operations, while the abstraction // defines higher-level operations based on those primitives. interface Device is method isEnabled() method enable() method disable() method getVolume() method setVolume(percent) method getChannel() method setChannel(channel) // All devices follow the same interface. class Tv implements Device is // ... class Radio implements Device is // ... // Somewhere in client code. tv = new Tv() remote = new RemoteControl(tv) remote.togglePower() radio = new Radio() remote = new AdvancedRemoteControl(radio)
Applicability
Use the Bridge pattern when
- You want to avoid a permanent binding between an abstraction and its implementation. This might be the case, for example, when the implementation must be selected or switched at run-time.
- Both the abstractions and their implementations should be extensible by subclassing. In this case, the Bridge pattern lets you combine the different abstractions and implementations and extend them independently.
- Changes in the implementation of an abstraction should have no impact on clients; that is, their code should not have to be recompiled.
- You have a proliferation of classes. Such a class hierarchy indicates the need for splitting an object into two parts. Rumbaugh uses the term “nested generalizations” to refer to such class hierarchies.
- You want to share an implementation among multiple objects (perhaps using reference counting), and this fact should be hidden from the client. A simple example is Coplien’s String class, in which multiple objects can share the same string representation
Comparisons
Click here to expand...
Link to originalAdapter Pattern vs Bridge Pattern:
- Bridge and Adapter both point at an existing type. But the bridge will point at an abstract type, and the adapter might point to a concrete type. The bridge will allow you to pair the implementation at runtime, whereas the adapter usually won’t
- Adapter makes things work after they’re designed; Bridge makes them work before they are.
- Bridge is designed up-front to let the abstraction and the implementation vary independently. Adapter is retrofitted to make unrelated classes work together
- Difference between Bridge pattern and Adapter pattern
- When do you use the Bridge Pattern? How is it different from Adapter pattern?
Link to originalBridge Pattern vs Strategy Pattern
- the UML class diagram for the Strategy is the same as the diagram for the Bridge. However, these two design patterns aren’t the same in their intent. While the Strategy pattern is meant for behavior, the Bridge pattern is meant for structure
- the coupling between the context and the strategies is tighter than the coupling between the abstraction and the implementation in the Bridge pattern
/structural-design-patterns/wrapper-patterns/bridge-pattern/structure-en-indexed-2x.png)
/structural-design-patterns/wrapper-patterns/bridge-pattern/example-en-2x.png)