The Bridge Pattern is a structural design pattern that is used to separate an abstraction from its implementation, allowing both to vary independently. This pattern is especially useful when you need to avoid a "polluted" inheritance structure and want to decouple abstraction and implementation in a way that they can evolve separately. It is an excellent choice when a system needs to be flexible and scalable.
Key Concepts:
- Abstraction:
The higher-level interface or class that defines the operations (abstract
class).
- RefinedAbstraction:
A subclass of the abstraction that can be refined or extended.
- Implementor:
The interface or class that provides the implementation (lower-level
interface).
- ConcreteImplementor:
A concrete subclass of the implementor that provides the actual
implementation.
The Bridge Pattern allows you to "bridge" the
abstraction with its implementation by separating them into two different
hierarchies: one for the abstraction and one for the implementation.
Example of Bridge Pattern in JavaScript
Imagine you have a graphic library that needs to support
different shapes (e.g., circles, squares) and different rendering methods
(e.g., canvas, SVG). Instead of creating multiple classes combining every
possible shape and rendering method (e.g., CircleCanvas, SquareCanvas, CircleSVG,
SquareSVG), you can use the Bridge Pattern to separate the shape (abstraction)
from the rendering method (implementation).
Without Bridge Pattern (Complex Inheritance):
class CircleCanvas {
draw() {
console.log("Drawing
circle on canvas");
}
}
class SquareCanvas {
draw() {
console.log("Drawing
square on canvas");
}
}
class CircleSVG {
draw() {
console.log("Drawing
circle in SVG");
}
}
class SquareSVG {
draw() {
console.log("Drawing square in
SVG");
}
}
// Multiple combinations lead to a complex inheritance
structure
let circleCanvas = new CircleCanvas();
circleCanvas.draw(); // Drawing circle on canvas
let squareSVG = new SquareSVG();
squareSVG.draw(); // Drawing square in SVG
This approach leads to a lot of classes that can be hard to
maintain and extend.
With Bridge Pattern (Simplified Structure):
With the Bridge Pattern, you separate the Shape
abstraction from the Rendering implementation. This allows you to mix
and match different shapes with different rendering methods.
// Implementor: Defines the rendering interface
class Renderer {
render(shape) {
throw new Error("This
method should be overridden!");
}
}
// ConcreteImplementor: Canvas rendering
class CanvasRenderer extends Renderer {
render(shape) {
console.log(`Rendering
${shape.getType()} on canvas`);
}
}
// ConcreteImplementor: SVG rendering
class SVGRenderer extends Renderer {
render(shape) {
console.log(`Rendering
${shape.getType()} in SVG`);
}
}
// Abstraction: Defines the shape interface
class Shape {
constructor(renderer)
{
this.renderer
= renderer; // Bridge to the implementation
}
draw() {
this.renderer.render(this);
}
getType() {
throw new Error("This
method should be overridden!");
}
}
// RefinedAbstraction: Circle shape
class Circle extends Shape {
constructor(renderer)
{
super(renderer);
}
getType() {
return "circle";
}
}
// RefinedAbstraction: Square shape
class Square extends Shape {
constructor(renderer)
{
super(renderer);
}
getType() {
return "square";
}
}
// Usage
const canvasRenderer = new CanvasRenderer();
const svgRenderer = new SVGRenderer();
const circleOnCanvas = new Circle(canvasRenderer);
circleOnCanvas.draw(); // Rendering circle on canvas
const squareOnSVG = new Square(svgRenderer);
squareOnSVG.draw(); // Rendering square in SVG
const circleOnSVG = new Circle(svgRenderer);
circleOnSVG.draw(); // Rendering circle in SVG
const squareOnCanvas = new Square(canvasRenderer);
squareOnCanvas.draw(); // Rendering square on canvas
Explanation:
- Renderer:
This is the Implementor interface that defines the render method, which
will be used by different shape objects to perform the actual rendering.
- CanvasRenderer
and SVGRenderer: These are concrete implementations of the Renderer
interface. They define how to render a shape on either canvas or SVG.
- Shape:
This is the Abstraction class. It holds a reference to the Renderer and
delegates the rendering task to the Renderer object. This class does not
know the details of how the rendering is done, only that it relies on the Renderer
object.
- Circle
and Square: These are RefinedAbstraction classes that define
specific shapes. They override the getType method to provide the type of
shape (circle or square).
With this pattern:
- The
abstraction (shape) can be modified independently of the implementation
(rendering methods).
- You
can easily add new shapes and rendering methods without altering the
existing code much, leading to a flexible and maintainable design.
Advantages of Bridge Pattern:
- Separation
of Concerns: The abstraction and implementation are separated, which
makes the code more modular and easier to maintain.
- Scalability:
New abstractions (shapes) or implementations (rendering methods) can be
added without altering existing code, just by extending the abstract
classes and creating new concrete implementations.
- Avoids
a Large Number of Subclasses: Without the Bridge Pattern, you would
need a subclass for each combination of abstraction and implementation.
With the Bridge Pattern, you only need subclasses for the abstraction and
the implementation, making the design cleaner.
When to Use the Bridge Pattern:
- When
you need to decouple an abstraction from its implementation so both can
evolve independently.
- When
you have multiple variations of objects and you want to avoid creating a
large number of subclasses to represent all possible combinations.
- When
you want to avoid an explosion of classes in cases where different
combinations of abstractions and implementations are needed.
No comments:
Post a Comment