Bridge pattern
From Wikipedia, the free encyclopedia
The bridge pattern is a design pattern used in software engineering which is meant to "decouple an abstraction from its implementation so that the two can vary independently" (Gamma et. al.). The bridge uses encapsulation, aggregation, and can use inheritance to separate responsibilities into different classes.
When a class varies often, the features of object-oriented programming become very useful because changes to a program's code can be made easily with minimal prior knowledge about the program. The bridge pattern is useful when not only the class itself varies often but also what the class does. The class itself can be thought of as the abstraction and what the class can do as the implementation.
Variant: The implementation can be decoupled even more by deferring the presence of the implementation to the point where the abstraction is utilized (as illustrated by the Visual Prolog example below).
Contents |
[edit] Non-Technical Examples
[edit] Shape Abstraction
When the abstraction and implementation are separated they can vary independently. Consider an abstraction such as shapes. There are many types of shapes and each with its own properties but there are things that all shapes do. One thing all shapes can do is draw themselves. However, drawing graphics to a screen can sometimes be dependent on different graphics implementations or operating systems. Shapes have to be able to be drawn on many types of systems, but having the shape itself implement them all or modifying the shape class to work with different architectures is not practical. The bridge helps by allowing the creation of new classes that provide the drawing implementation. The abstract - shape - class provides methods for getting the size or properties of a shape while the implementation - drawing - class provides an interface for drawing graphics. Now if a new shape needs to be created or there is a new graphics API to be drawn on, then it is very easy to add a new class that implements the needed features.[1]
[edit] Car Abstraction
Imagine two types of cars (the abstraction), a Jaguar and a Mercedes (both are Refinements of the Abstraction). The Abstraction defines that a Car has features such as tires and an engine. A Refinements of the Abstraction declare what specific kind of tires and engine it has.
Finally, there are two types of road. The road is the Implementor (see image below). A highway and an interstate highway are the Implementation Details. Any car refinement needs to be able to drive on any type of road; this concept is what the Bridge Pattern is all about.
[edit] Structure
- Client
- The Object using the bridge pattern
- Abstraction
- defines the abstract interface
- maintains the implementor reference
- Refined Abstraction
- Extends the interface defined by Abstraction
- Implementor
- defines the interface for implementation classes. (Typically the Abstraction interface defines higher level operations based on this interface operations)
- ConcreteImplementor
- implements the Implementor interface
[edit] Code Examples
[edit] C#
The following C# program illustrates the 'shape' example given above and will output:
API1.circle at 1:2 radius 7.5 API2.circle at 5:7 radius 27.5
using System; /** "Implementor" */ interface DrawingAPI { void DrawCircle(double x, double y, double radius); } /** "ConcreteImplementor" 1/2 */ class DrawingAPI1 : DrawingAPI { public void DrawCircle(double x, double y, double radius) { System.Console.WriteLine("API1.circle at {0}:{1} radius {2}\n", x, y, radius); } } /** "ConcreteImplementor" 2/2 */ class DrawingAPI2 : DrawingAPI { public void DrawCircle(double x, double y, double radius) { System.Console.WriteLine("API2.circle at {0}:{1} radius {2}\n", x, y, radius); } } /** "Abstraction" */ interface Shape { void Draw(); // low-level void ResizeByPercentage(double pct); // high-level } /** "Refined Abstraction" */ class CircleShape : Shape { private double x, y, radius; private DrawingAPI drawingAPI; public CircleShape(double x, double y, double radius, DrawingAPI drawingAPI) { this.x = x; this.y = y; this.radius = radius; this.drawingAPI = drawingAPI; } // low-level i.e. Implementation specific public void Draw() { drawingAPI.DrawCircle(x, y, radius); } // high-level i.e. Abstraction specific public void ResizeByPercentage(double pct) { radius *= pct; } } /** "Client" */ class BridgePattern { public static void Main(string[] args) { Shape[] shapes = new Shape[2]; shapes[0] = new CircleShape(1, 2, 3, new DrawingAPI1()); shapes[1] = new CircleShape(5, 7, 11, new DrawingAPI2()); foreach (Shape shape in shapes) { shape.ResizeByPercentage(2.5); shape.Draw(); } } }
[edit] Ruby
This will print:
API1.circle at 1.000000:2.000000 radius 7.500000 API2.circle at 5.000000:7.000000 radius 27.500000
class CircleShape def initialize(x, y, r, da) @x, @y, @r, @da = x, y, r, da end def draw @da.draw_circle(@x, @y, @r) end def resize_by_percentage(pct) @r *= pct end end class DrawingAPI1 def draw_circle(x, y, r) printf "API1.circle at %f:%f radius %f\n", x, y, r end end class DrawingAPI2 def draw_circle(x, y, r) printf "API2.circle at %f:%f radius %f\n", x, y, r end end shapes = [CircleShape.new(1, 2, 3, DrawingAPI1.new), CircleShape.new(5, 7, 11, DrawingAPI2.new)] shapes.each do |shape| shape.resize_by_percentage(2.5) shape.draw end
This is translated from the Python example. DrawingAPI1 and DrawingAPI2 can be implemented more elegantly as blocks.
[edit] Visual Prolog
This example illustrates the variant where the presence of the implementation is deferred to the point where the model is utilized.
Abstract implementor
interface drawingApi predicates drawCircle : (real X, real Y, real Radius). drawSquare : (real X, real Y, real Width). end interface drawingApi
Concrete implementor 1 (of 2)
class openGL : drawingApi end class openGL implement openGL clauses drawCircle(X, Y, Radius) :- stdio::writef("OpenGL circle at (%,%) with radius=%\n", X, Y, Radius). clauses drawSquare(X, Y, Width) :- stdio::writef("OpenGL square at (%,%) with width=%\n", X, Y, Width). end implement openGL
Concrete implementor 2 (of 2)
class directX : drawingApi end class directX % directX (trivial) implementation skipped
Abstract shape data model
interface shape predicates draw : (drawingApi DrawingAPI). end interface shape
class circle : shape constructors new : (real X, real Y, real Radius). end class circle implement circle facts x : real. y: real. radius : real. clauses new(X, Y, Radius) :- x := X, y := Y, radius := Radius. clauses draw(DrawingAPI) :- DrawingAPI:drawCircle(x, y, radius). end implement circle
class square : shape constructors new : (real X, real Y, real Width). end class square implement square facts x : real. y: real. width : real. clauses new(X, Y, Width) :- x := X, y := Y, width := Width. clauses draw(DrawingAPI) :- DrawingAPI:drawSquare(x, y, width). end implement square
Client code
goal console::init(), % The concrete model is a list of shapes ConcreteModel = [circle::new(1, 2, 7.5), circle::new(5, 7, 27.5), square::new(2.9, 80, 17)], % draw using OpenGL OpenGL = openGL::new(), foreach S1 = list::getMember_nd(ConcreteModel) do S1:draw(OpenGL) end foreach, % draw the same model using DirectX DirectX = directX::new(), foreach S2 = list::getMember_nd(ConcreteModel) do S2:draw(DirectX) end foreach.
[edit] See also
[edit] References
- ^ Shalloway; Trott. Design Patterns Explained: A New Perspective on Object-Oriented Design.
Creational: Abstract factory • Builder • Factory • Prototype • Singleton
Structural: Adapter • Bridge • Composite • Decorator • Façade • Flyweight • Proxy
Behavorial: Chain of responsibility • Command • Interpreter • Iterator • Mediator • Memento • Observer • State • Strategy • Template method • Visitor
Categories: Software design patterns | Articles with example Java code | Articles with example C++ code | Articles with example C Sharp code | Articles with example Perl code | Articles with example PHP code | Articles with example Python code | Articles with example Ruby code | Articles with example Visual Prolog code