Imagine you are working on an interface for buyers to spec out new vehicles and it is time to work on
the Interior configuration. For now requirements state that customers can only pick one of two interior colors
(black or beige) and one of two materials (cloth or leather). It may change though, but it's not certain.
Then the code for each of the interiors would look something like that:
class CarInterior {
model: string;
interiorColor: string;
interiorMaterial: string;
constructor(model: string) {
this.model = model;
}
getSummary = () =>
console.log(
`Model: ${this.model}, ${this.interiorColor} ${this.interiorMaterial} interior.`
);
}
class BlackLeatherInterior extends CarInterior {
constructor(model: string) {
super(model);
this.model = model;
this.interiorColor = "black";
this.interiorMaterial = "leather";
}
}
class BeigeLeatherInterior extends CarInterior {
constructor(model: string) {
super(model);
this.model = model;
this.interiorColor = "beige";
this.interiorMaterial = "leather";
}
}
class BlackClothInterior extends CarInterior {
constructor(model: string) {
super(model);
this.model = model;
this.interiorColor = "black";
this.interiorMaterial = "cloth";
}
}
class BeigeClothInterior extends CarInterior {
constructor(model: string) {
super(model);
this.model = model;
this.interiorColor = "beige";
this.interiorMaterial = "cloth";
}
}
This testing code returns this:
let bl = new BlackLeatherInterior("A");
let rc = new BeigeClothInterior("B");
let bc = new BlackClothInterior("C");
bl.getSummary();
rc.getSummary();
bc.getSummary();
//output:
Model: A, black leather interior.
Model: B, beige cloth interior.
Model: C, black cloth interior.
And then you got a word that another interior color may be available. Also, it has to be customizable for different car models.
Creating a class for each combination of resources will quickly become unwieldy. There has to be a better way!
Bridge pattern is applicable when you can separate a component into parts reducing coupling. One of the parts
is deemed as the "abstraction" and it will be sort of a foundation on which everything else is built. Every other
variable aspect is deemed "implementation" and abstractions and implementations can vary independently.
It is almost like making a bead bracelet: the string is the abstraction, it holds everything together,
but it does not care what beads you put on, it will still be a bead bracelet.
To rework the previous example, let's deem the Interior class as the Abstraction. Any vehicle has an interior,
and everything else will be considered "implementation" and vary as needed.
class CarInterior {
interiorColor: InteriorColor;
interiorMaterial: InteriorMaterial;
model: string;
constructor(
model: string,
color: InteriorColor,
material: InteriorMaterial
) {
this.model = model;
this.interiorColor = color;
this.interiorMaterial = material;
}
getSummary = () =>
console.log(
`Model: ${this.model}, ${this.interiorColor.interiorColor} ${this.interiorMaterial.interiorMaterial} interior.`
);
}
Let's specify the InteriorColor and InteriorMaterial interfaces that will help color and material implementations vary:
interface InteriorColor {
interiorColor: string;
}
interface InteriorMaterial {
interiorMaterial: string;
}
Next, separate classes that implement those interfaces and focus on only one aspect:
class BlackInterior implements InteriorColor {
interiorColor: string;
constructor() {
this.interiorColor = "black";
}
}
class BeigeInterior implements InteriorColor {
interiorColor: string;
constructor() {
this.interiorColor = "beige";
}
}
class LeatherInterior implements InteriorMaterial {
constructor() {
this.interiorMaterial = "leather";
}
interiorMaterial: string;
}
class ClothInterior implements InteriorMaterial {
interiorMaterial: string;
constructor() {
this.interiorMaterial = "cloth";
}
}
Here's some test code showing that the result stayed the same:
let bl = new CarInterior("D", new BlackInterior(), new LeatherInterior());
let rc = new CarInterior("E", new BeigeInterior(), new ClothInterior());
let bc = new CarInterior("F", new BlackInterior(), new ClothInterior());
//output:
Model: D, black leather interior.
Model: E, beige cloth interior.
Model: F, black cloth interior.
And, say, there's a new requirement that some interiors will be made of cloth/leather combo. With Bridge,
such variation is expected and is not that difficult to handle. The only thing that needs to happen is
create a new class to implement the combo material, and then it's ready to be tested:
class ClothLeatherComboInterior implements InteriorMaterial {
interiorMaterial: string;
constructor() {
this.interiorMaterial = "cloth/leather combo";
}
}
let rcl = new CarInterior("G", new BeigeInterior(), new ClothLeatherComboInterior());
rcl.getSummary();
//output
Model: G, beige cloth/leather combo interior.
Ultimately, we went from a flat, tightly coupled hierarchy like this:
to a more decoupled hierarchy like this:
Needless to say, that adding new features to interior, such as climate control,
or ventilated front seats will be pretty easy.
Bridge pattern really emphasizes Composition over Inheritance and it allows multiple aspects to
be developed independently while supporting interactions via an existing interface. Other examples
are remotes and TVs -- remotes can vary in shape and size, and TVs can be bigger or smaller or use
different resolution/technology, yet a remote can turn a TV on and off and adjust volume.
Another example
would be a car driver. Cars change every year and a driver no longer worries about what's under a car's hood
and how a drive train operates on a particular model. All they know is that they need to get into the vehicle,
shift gear to "D" and steer. At the same time, car does not care about who drives it, seats can adjust to support
a variety of bodies.
Also, the Bluetooth audio interface is a great example of the Bridge pattern.
You can hook up any device to a Bluetooth receiver, and it can be a phone, or a tablet, and Bluetooth
speakers will provide audio output. Also, devices do not really care what speaker blasts their output,
it can be anything from headphones to sound bars, sound is coming out all the same.