The bridge pattern is a design pattern used in software engineering that is meant to "decouple an abstraction from its implementation so that the two can vary independently", introduced by the Gang of Four. [1] 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 both the class and what it does vary often. The class itself can be thought of as the abstraction and what the class can do as the implementation. The bridge pattern can also be thought of as two layers of abstraction.
When there is only one fixed implementation, this pattern is known as the Pimpl idiom in the C++ world.
The bridge pattern is often confused with the adapter pattern, and is often implemented using the object adapter pattern; e.g., in the Java code below.
Variant: The implementation can be decoupled even more by deferring the presence of the implementation to the point where the abstraction is utilized.
The Bridge design pattern is one of the twenty-three well-known GoF design patterns that describe how to solve recurring design problems to design flexible and reusable object-oriented software, that is, objects that are easier to implement, change, test, and reuse. [1]
What problems can the Bridge design pattern solve? [2]
When using subclassing, different subclasses implement an abstract class in different ways. But an implementation is bound to the abstraction at compile-time and cannot be changed at run-time.
What solution does the Bridge design pattern describe?
Abstraction
) from its implementation (Implementor
) by putting them in separate class hierarchies.Abstraction
in terms of (by delegating to) an Implementor
object.This enables to configure an Abstraction
with an Implementor
object at run-time.
See also the Unified Modeling Language class and sequence diagram below.
In the above Unified Modeling Language class diagram, an abstraction (Abstraction
) is not implemented as usual in a single inheritance hierarchy. Instead, there is one hierarchy for an abstraction (Abstraction
) and a separate hierarchy for its implementation (Implementor
), which makes the two independent from each other. The Abstraction
interface (operation()
) is implemented in terms of (by delegating to) the Implementor
interface (imp.operationImp()
).
The UML sequence diagram shows the run-time interactions: The Abstraction1
object delegates implementation to the Implementor1
object (by calling operationImp()
on Implementor1
), which performs the operation and returns to Abstraction1
.
Bridge pattern compose objects in tree structure. It decouples abstraction from implementation. Here abstraction represents the client from which the objects will be called. An example implemented in C# is given below
// Helps in providing truly decoupled architecturepublicinterfaceIBridge{voidFunction1();voidFunction2();}publicclassBridge1:IBridge{publicvoidFunction1(){Console.WriteLine("Bridge1.Function1");}publicvoidFunction2(){Console.WriteLine("Bridge1.Function2");}}publicclassBridge2:IBridge{publicvoidFunction1(){Console.WriteLine("Bridge2.Function1");}publicvoidFunction2(){Console.WriteLine("Bridge2.Function2");}}publicinterfaceIAbstractBridge{voidCallMethod1();voidCallMethod2();}publicclassAbstractBridge:IAbstractBridge{publicIBridgebridge;publicAbstractBridge(IBridgebridge){this.bridge=bridge;}publicvoidCallMethod1(){this.bridge.Function1();}publicvoidCallMethod2(){this.bridge.Function2();}}
The Bridge classes are the Implementation that uses the same interface-oriented architecture to create objects. On the other hand, the abstraction takes an instance of the implementation class and runs its method. Thus, they are completely decoupled from one another.
abstractclassDrawingAPIabstractdefdraw_circle(x:Float64,y:Float64,radius:Float64)endclassDrawingAPI1<DrawingAPIdefdraw_circle(x:Float,y:Float,radius:Float)"API1.circle at #{x}:#{y} - radius: #{radius}"endendclassDrawingAPI2<DrawingAPIdefdraw_circle(x:Float64,y:Float64,radius:Float64)"API2.circle at #{x}:#{y} - radius: #{radius}"endendabstractclassShapeprotectedgetterdrawing_api:DrawingAPIdefinitialize(@drawing_api)endabstractdefdrawabstractdefresize_by_percentage(percent:Float64)endclassCircleShape<Shapegetterx:Float64gettery:Float64getterradius:Float64definitialize(@x,@y,@radius,drawing_api:DrawingAPI)super(drawing_api)enddefdraw@drawing_api.draw_circle(@x,@y,@radius)enddefresize_by_percentage(percent:Float64)@radius*=(1+percent/100)endendclassBridgePatterndefself.testshapes=[]ofShapeshapes<<CircleShape.new(1.0,2.0,3.0,DrawingAPI1.new)shapes<<CircleShape.new(5.0,7.0,11.0,DrawingAPI2.new)shapes.eachdo|shape|shape.resize_by_percentage(2.5)putsshape.drawendendendBridgePattern.test
Output
API1.circle at 1.0:2.0 - radius: 3.075 API2.circle at 5.0:7.0 - radius: 11.275
importstd;classDrawingAPI{public:virtual~DrawingAPI()=default;virtualstd::stringDrawCircle(floatx,floaty,floatradius)const=0;};classDrawingAPI01:publicDrawingAPI{public:std::stringDrawCircle(floatx,floaty,floatradius)constoverride{returnstd::format("API01.circle at {}:{} - radius: {}",std::to_string(x),std::to_string(y),std::to_string(radius));}};classDrawingAPI02:publicDrawingAPI{public:std::stringDrawCircle(floatx,floaty,floatradius)constoverride{returnstd::format("API02.circle at {}:{} - radius: {}",std::to_string(x),std::to_string(y),std::to_string(radius));}};classShape{public:Shape(constDrawingAPI&drawing_api):drawing_api_(drawing_api){}virtual~Shape()=default;virtualstd::stringDraw()const=0;virtualfloatResizeByPercentage(constfloatpercent)=0;protected:constDrawingAPI&drawing_api_;};classCircleShape:publicShape{public:CircleShape(floatx,floaty,floatradius,constDrawingAPI&drawing_api):Shape(drawing_api),x_(x),y_(y),radius_(radius){}std::stringDraw()constoverride{returndrawing_api_.DrawCircle(x_,y_,radius_);}floatResizeByPercentage(constfloatpercent)override{returnradius_*=(1.0f+percent/100.0f);}private:floatx_,y_,radius_;};intmain(intargc,char**argv){constDrawingAPI01api1{};constDrawingAPI02api2{};std::vector<CircleShape>shapes{CircleShape{1.0f,2.0f,3.0f,api1},CircleShape{5.0f,7.0f,11.0f,api2}};for(CircleShape&shape:shapes){shape.ResizeByPercentage(2.5);std::println("{}",shape.Draw());}return0;}
Output:
API01.circle at 1.000000:2.000000 - radius: 3.075000 API02.circle at 5.000000:7.000000 - radius: 11.275000
The following Java program defines a bank account that separates the account operations from the logging of these operations.
// Logger has two implementations: info and warning@FunctionalInterfaceinterfaceLogger{voidlog(Stringmessage);staticLoggerinfo(){returnmessage->System.out.println("info: "+message);}staticLoggerwarning(){returnmessage->System.out.println("warning: "+message);}}abstractclassAbstractAccount{privateLoggerlogger=Logger.info();publicvoidsetLogger(Loggerlogger){this.logger=logger;}// the logging part is delegated to the Logger implementationprotectedvoidoperate(Stringmessage,booleanresult){logger.log(message+" result "+result);}}classSimpleAccountextendsAbstractAccount{privateintbalance;publicSimpleAccount(intbalance){this.balance=balance;}publicbooleanisBalanceLow(){returnbalance<50;}publicvoidwithdraw(intamount){booleanshouldPerform=balance>=amount;if(shouldPerform){balance-=amount;}operate("withdraw "+amount,shouldPerform);}}publicclassBridgeDemo{publicstaticvoidmain(String[]args){SimpleAccountaccount=newSimpleAccount(100);account.withdraw(75);if(account.isBalanceLow()){// you can also change the Logger implementation at runtimeaccount.setLogger(Logger.warning());}account.withdraw(10);account.withdraw(100);}}
It will output:
info: withdraw 75 result true warning: withdraw 10 result true warning: withdraw 100 result false
interfaceDrawingAPI{functiondrawCircle($x,$y,$radius);}classDrawingAPI1implementsDrawingAPI{publicfunctiondrawCircle($x,$y,$radius){echo"API1.circle at $x:$y radius $radius.\n";}}classDrawingAPI2implementsDrawingAPI{publicfunctiondrawCircle($x,$y,$radius){echo"API2.circle at $x:$y radius $radius.\n";}}abstractclassShape{protected$drawingAPI;publicabstractfunctiondraw();publicabstractfunctionresizeByPercentage($pct);protectedfunction__construct(DrawingAPI$drawingAPI){$this->drawingAPI=$drawingAPI;}}classCircleShapeextendsShape{private$x;private$y;private$radius;publicfunction__construct($x,$y,$radius,DrawingAPI$drawingAPI){parent::__construct($drawingAPI);$this->x=$x;$this->y=$y;$this->radius=$radius;}publicfunctiondraw(){$this->drawingAPI->drawCircle($this->x,$this->y,$this->radius);}publicfunctionresizeByPercentage($pct){$this->radius*=$pct;}}classTester{publicstaticfunctionmain(){$shapes=array(newCircleShape(1,3,7,newDrawingAPI1()),newCircleShape(5,7,11,newDrawingAPI2()),);foreach($shapesas$shape){$shape->resizeByPercentage(2.5);$shape->draw();}}}Tester::main();
Output:
API1.circle at 1:3 radius 17.5 API2.circle at 5:7 radius 27.5
traitDrawingAPI{defdrawCircle(x:Double,y:Double,radius:Double)}classDrawingAPI1extendsDrawingAPI{defdrawCircle(x:Double,y:Double,radius:Double)=println(s"API #1 $x$y$radius")}classDrawingAPI2extendsDrawingAPI{defdrawCircle(x:Double,y:Double,radius:Double)=println(s"API #2 $x$y$radius")}abstractclassShape(drawingAPI:DrawingAPI){defdraw()defresizePercentage(pct:Double)}classCircleShape(x:Double,y:Double,varradius:Double,drawingAPI:DrawingAPI)extendsShape(drawingAPI:DrawingAPI){defdraw()=drawingAPI.drawCircle(x,y,radius)defresizePercentage(pct:Double){radius*=pct}}objectBridgePattern{defmain(args:Array[String]){Seq(newCircleShape(1,3,5,newDrawingAPI1),newCircleShape(4,5,6,newDrawingAPI2))foreach{x=>x.resizePercentage(3)x.draw()}}}
"""Bridge pattern example."""fromabcimportABCMeta,abstractmethodNOT_IMPLEMENTED="You should implement this."classDrawingAPI:__metaclass__=ABCMeta@abstractmethoddefdraw_circle(self,x,y,radius):raiseNotImplementedError(NOT_IMPLEMENTED)classDrawingAPI1(DrawingAPI):defdraw_circle(self,x,y,radius):returnf"API1.circle at {x}:{y} - radius: {radius}"classDrawingAPI2(DrawingAPI):defdraw_circle(self,x,y,radius):returnf"API2.circle at {x}:{y} - radius: {radius}"classDrawingAPI3(DrawingAPI):defdraw_circle(self,x,y,radius):returnf"API3.circle at {x}:{y} - radius: {radius}"classShape:__metaclass__=ABCMetadrawing_api=Nonedef__init__(self,drawing_api):self.drawing_api=drawing_api@abstractmethoddefdraw(self):raiseNotImplementedError(NOT_IMPLEMENTED)@abstractmethoddefresize_by_percentage(self,percent):raiseNotImplementedError(NOT_IMPLEMENTED)classCircleShape(Shape):def__init__(self,x,y,radius,drawing_api):self.x=xself.y=yself.radius=radiussuper(CircleShape,self).__init__(drawing_api)defdraw(self):returnself.drawing_api.draw_circle(self.x,self.y,self.radius)defresize_by_percentage(self,percent):self.radius*=1+percent/100classBridgePattern:@staticmethoddeftest():shapes=[CircleShape(1.0,2.0,3.0,DrawingAPI1()),CircleShape(5.0,7.0,11.0,DrawingAPI2()),CircleShape(5.0,4.0,12.0,DrawingAPI3()),]forshapeinshapes:shape.resize_by_percentage(2.5)print(shape.draw())BridgePattern.test()
A visitor pattern is a software design pattern that separates the algorithm from the object structure. Because of this separation, new operations can be added to existing object structures without modifying the structures. It is one way to follow the open/closed principle in object-oriented programming and software engineering.
The abstract factory pattern in software engineering is a design pattern that provides a way to create families of related objects without imposing their concrete classes, by encapsulating a group of individual factories that have a common theme without specifying their concrete classes. According to this pattern, a client software component creates a concrete implementation of the abstract factory and then uses the generic interface of the factory to create the concrete objects that are part of the family. The client does not know which concrete objects it receives from each of these internal factories, as it uses only the generic interfaces of their products. This pattern separates the details of implementation of a set of objects from their general usage and relies on object composition, as object creation is implemented in methods exposed in the factory interface.
In object-oriented programming, the factory method pattern is a design pattern that uses factory methods to deal with the problem of creating objects without having to specify their exact classes. Rather than by calling a constructor, this is accomplished by invoking a factory method to create an object. Factory methods can be specified in an interface and implemented by subclasses or implemented in a base class and optionally overridden by subclasses. It is one of the 23 classic design patterns described in the book Design Patterns and is subcategorized as a creational pattern.
In software engineering, the composite pattern is a partitioning design pattern. The composite pattern describes a group of objects that are treated the same way as a single instance of the same type of object. The intent of a composite is to "compose" objects into tree structures to represent part-whole hierarchies. Implementing the composite pattern lets clients treat individual objects and compositions uniformly.
In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other instances of the same class. The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern as well as to the Open-Closed Principle, by allowing the functionality of a class to be extended without being modified. Decorator use can be more efficient than subclassing, because an object's behavior can be augmented without defining an entirely new object.
In computer programming, the interpreter pattern is a design pattern that specifies how to evaluate sentences in a language. The basic idea is to have a class for each symbol in a specialized computer language. The syntax tree of a sentence in the language is an instance of the composite pattern and is used to evaluate (interpret) the sentence for a client. See also Composite pattern.
In software design and engineering, the observer pattern is a software design pattern in which an object, named the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.
In object-oriented (OO) and functional programming, an immutable object is an object whose state cannot be modified after it is created. This is in contrast to a mutable object, which can be modified after it is created. In some cases, an object is considered immutable even if some internally used attributes change, but the object's state appears unchanging from an external point of view. For example, an object that uses memoization to cache the results of expensive computations could still be considered an immutable object.
A method in object-oriented programming (OOP) is a procedure associated with an object, and generally also a message. An object consists of state data and behavior; these compose an interface, which specifies how the object may be used. A method is a behavior of an object parametrized by a user.
In mathematics and computer science, a higher-order function (HOF) is a function that does at least one of the following:
In object-oriented programming such as is often used in C++ and Object Pascal, a virtual function or virtual method is an inheritable and overridable function or method that is dispatched dynamically. Virtual functions are an important part of (runtime) polymorphism in object-oriented programming (OOP). They allow for the execution of target functions that were not precisely identified at compile time.
In computer science, a tagged union, also called a variant, variant record, choice type, discriminated union, disjoint union, sum type, or coproduct, is a data structure used to hold a value that could take on several different, but fixed, types. Only one of the types can be in use at any one time, and a tag field explicitly indicates which type is in use. It can be thought of as a type that has several "cases", each of which should be handled correctly when that type is manipulated. This is critical in defining recursive datatypes, in which some component of a value may have the same type as that value, for example in defining a type for representing trees, where it is necessary to distinguish multi-node subtrees and leaves. Like ordinary unions, tagged unions can save storage by overlapping storage areas for each type, since only one is in use at a time.
In computer programming, a function object is a construct allowing an object to be invoked or called as if it were an ordinary function, usually with the same syntax. In some languages, particularly C++, function objects are often called functors.
Method overriding, in object-oriented programming, is a language feature that allows a subclass or child class to provide a specific implementation of a method that is already provided by one of its superclasses or parent classes. In addition to providing data-driven algorithm-determined parameters across virtual network interfaces, it also allows for a specific type of polymorphism (subtyping). The implementation in the subclass overrides (replaces) the implementation in the superclass by providing a method that has same name, same parameters or signature, and same return type as the method in the parent class. The version of a method that is executed will be determined by the object that is used to invoke it. If an object of a parent class is used to invoke the method, then the version in the parent class will be executed, but if an object of the subclass is used to invoke the method, then the version in the child class will be executed. This helps in preventing problems associated with differential relay analytics which would otherwise rely on a framework in which method overriding might be obviated. Some languages allow a programmer to prevent a method from being overridden.
The curiously recurring template pattern (CRTP) is an idiom, originally in C++, in which a class X
derives from a class template instantiation using X
itself as a template argument. More generally it is known as F-bound polymorphism, and it is a form of F-bounded quantification.
A property, in some object-oriented programming languages, is a special sort of class member, intermediate in functionality between a field and a method. The syntax for reading and writing of properties is like for fields, but property reads and writes are (usually) translated to 'getter' and 'setter' method calls. The field-like syntax is easier to read and write than many method calls, yet the interposition of method calls "under the hood" allows for data validation, active updating, or implementation of what may be called "read-only fields".
In software engineering, a fluent interface is an object-oriented API whose design relies extensively on method chaining. Its goal is to increase code legibility by creating a domain-specific language (DSL). The term was coined in 2005 by Eric Evans and Martin Fowler.
In object-oriented design, the chain-of-responsibility pattern is a behavioral design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain. A mechanism also exists for adding new processing objects to the end of this chain.
In computer graphics, the centripetal Catmull–Rom spline is a variant form of the Catmull–Rom spline, originally formulated by Edwin Catmull and Raphael Rom, which can be evaluated using a recursive algorithm proposed by Barry and Goldman. It is a type of interpolating spline defined by four control points , with the curve drawn only from to .
In software engineering, the Twin pattern is a software design pattern that allows developers to model multiple inheritance in programming languages that do not support multiple inheritance. This pattern avoids many of the problems with multiple inheritance.
{{cite book}}
: |work=
ignored (help) From: James W. Cooper (2003). C# Design Patterns: A Tutorial. Addison-Wesley. ISBN 0-201-84453-2.