Upload
khangminh22
View
1
Download
0
Embed Size (px)
Citation preview
CSCI-4448 Boese
Objectives
• Definition
• Why
• How
• Bridge Examples
– User Interfaces
– Tic tac toe!
• Design Considerations
• Comparisons with Adapter
CSCI-4448 Boese
Definition
“Decouple an abstraction from its implementation
so that the two can vary independently.”
-Gang of Four
CSCI-4448 Boese
Definition
• Name “Bridge” – A device that is used to connect two pieces of equipment
that were not designed to be connected
Abstraction Implementation
• Intent – Decouple an abstraction from an implementation
– Allows these two perspective of a class to be developed independently
CSCI-4448 Boese
Definition Abstraction
• “What you want your code to do”
– Draw a 3D sphere
– Draw a 3D cone
– Draw a 3D snowman
Implementation
• “The many ways that happen”
• OpenGL
– glVertex
• Direct3D
– Triangles
• Raytracing
– Shperes
CSCI-4448 Boese
Why a Bridge?
• Inheritance is a typical method for providing an
abstract representation of something, and
allowing for varying details using concrete
subclasses
• We can work with our objects in terms of the
abstract class, but specialize to the desired
details of a specific subclass
• So, why introduce yet another class for this
concept? Isn’t it inherent in OO languages?
CSCI-4448 Boese
Inheritance Problem
• Lump all the implementation details into a single
abstraction • Shape3D is the abstraction, Sphere, Code and Snowman
are implementations
Abstract
Concept
Concrete Versions
CSCI-4448 Boese
Inheritance Problem
• But that isn’t the full implementation details! • Need to have Raytaced Snowmen and Direct3D cones…
Abstract Concept More Specific
Abstract Concept
Concrete
Implementations
Concrete
Implementations
CSCI-4448 Boese
Inheritance Problem
• Class explosion! • 3 possible shapes x 3 possible rendering methods =
9 concrete classes and 4 abstract classes
– Add in a TextureMap and Shader options
• RayTraceSnowmanWithTextureMapAndNoShader
• 3 shapes x 3 rendering methods x 2 texture maps x 2
shaders = 36 concrete classes, 28 abstract classes
• Add a single abstract method to the original Shape3D
class, need to update 64 classes!!!
• This is one reason why to Favor Delegation
over Inheritance
CSCI-4448 Boese
Why Use a Bridge?
• Inheritance leads us down a very dangerous
path…
– As features get added, the inheritance hierarchy grows
exponentially in number of implementation details
– Also, the actual class that gets instantiated is bound to
the implementation details
• Can’t dynamically change a RaytraceSnowman to
OpenGLSnowman…
– Violates the Single Responsibility Principle
• A class should have only one reason to change (subclass)
• Our classes change based on shape, rendering method,
shader and texture map option…
CSCI-4448 Boese
Bridge Pattern – Why
• Avoid permanent binding between an
abstraction and its implementation
– Can change from raytraced Snowman to OpenGL
Snowman during run-time
• Otherwise, change instance from RayTracedSnowman to
OpenGLSnowman and recompile
• Extend abstraction and implementation
independently • Add a new shape to the family of Shape3D, don’t need to
change the various rendering classes
• Add a new rendering method, doesn’t affect the shapes
CSCI-4448 Boese
Bridge Pattern - Why
• Cohesion – a class should encapsulate a single
changeable concept
– Shape3D instances should be responsible for
geometry-related tasks only
– Rendering should be encapsulated into a separate
set of classes
• Reduce maintenance costs
– Reduced number of classes
• Pure Inheritance – product of the number of features
• Bridge – sum of the number of features
CSCI-4448 Boese
Bridge Pattern - Participants
• Abstraction – Defines the abstraction’s interface
– Maintains reference to an instance of implementor
• Implementor – Defines the interface for implementation classes
– Doesn’t need to correspond to Abstraction’s interface • Usually, consists of more primitive operations
• Refined Abstraction – Extends interface defined by abstraction
• Concrete Implementation – Implements the Implementor interface
CSCI-4448 Boese
Game Example – Drawing the Board
• Recall the original Tic-tac-toe game
– Created a nice procedural version for Tic-tac-toe
– Then, added different games
• Connect 4, Battleship, etc.
CSCI-4448 Boese
Game Example
• Then, need to draw each of these games on
different platform • Graphics, Swing,
Android, iOS, …
• Interfaces: Graphic vs Swing
• Swing: paintComponent
• Graphic: drawLine
CSCI-4448 Boese
Apply Bridge Pattern
• Obviously, we’re going down the wrong path
– Using inheritance to capture every implementation
detail
– Abstractly, we want to be able to draw the different
types of boards
• Requires a certain set of operations
– On the implementation level, we want to be able to
draw a common set of primitives, regardless of what
we’re drawing on
• Requires a possibly different set of operations
CSCI-4448 Boese
Defining Abstraction
• Abstract operations • displayLine, displayCircle, displayText, displayImage
• Tic-tac-toe operations • drawX, drawO, drawGrid
• Uses displayLine and displayText
• Connect 4 operations • drawBackground and drawToken
• uses displayImage and displayCircle
Specialized operations of
the various abstractions,
NOT implementation
operations!
CSCI-4448 Boese
Defining Implementor
• What are the common drawing operations
desired?
– drawCircle, drawImage, drawText, drawLine…
• Is this the same as the Abstraction’s interface?
– Not really. These are things the Implementors are
capable of doing, not necessarily what the Abstraction
needs them to do
• Add displayRectangle to Abstraction. Abstraction
implements this by calling drawLine four times, not by
adding a drawRectangle in the Implementor
CSCI-4448 Boese
Putting it all together
• The Game Client uses GameBoard – Polymorphic, doesn’t care what the GameBoard is, just that it can drawBoard()
• Specific GameBoard instances use refined Graphics abstraction to do the drawing
– Contains common drawing operations and specific operations for each game
• Graphics doesn’t care what’s actually being drawn on – Bridge decouples the implementation details
CSCI-4448 Boese
Consequences
• Decouples Interface and Implementation – Implementation details are not bound to a particular
interface
• Can change an object’s implementation at run-time!
– Eliminate compile-time dependencies
• Change Implementor – no need to recompile Abstraction
– Provide a layering between high-level and low-level components
• Improves extensibility – Just refine the abstraction, or add an implementor,
independently
• Hide implementation details from clients – Data Hiding is just one form of encapsulation
CSCI-4448 Boese
Bridge vs. Adapter
• Bridge decouples an abstraction from its
implementation, allowing the abstraction’s
interface to work with an implementation
interface
• Adapter allows some client’s interface to interact
with some implementation’s interface
• What’s the difference…?
CSCI-4448 Boese
Bridge vs. Adapter
• From GoF “The key difference between these patterns lie in their intents. Adapter focuses on resolving incompatibilities between two existing interfaces. It doesn’t focus on how those interfaces are implemented, nor does it consider how they might evolve independently. It’s a way of making two independently designed classes work together without reimplementing one or the other. Bridge, on the other hand, bridges an abstraction and its (potentially numerous) implementations. It provides a stable interface to clients even as it lets you vary the classes that implement it. It also accommodates new implementations as the system evolves.”
CSCI-4448 Boese
Bridge vs. Adapter
• TL;DR
– The purpose of an adapter is to allow two existing
classes work together
• The coupling was unforseen
– The purpose of a bridge is to allow the abstraction of
something to evolve separate from the
implementation
• The decoupling process is performed up-front
– In practice, these occur in different stages of SLC
• Adapter makes things work after they’re designed
• Bridge makes things work before they’re designed
• Adapter may become Bridge in future refactorings