Chapter24 Bridge
Chapter24 Bridge
Contents
24.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
24.4 Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
24.5 Exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1
24.1 Introduction
Separation of concerns is a general problem-solving idiom that enables us to break the
complexity of a problem into loosely-coupled, easier to solve, subproblems [3]. When
applied to software design it results in a design where the classes are cohesive and loosely-
coupled.
The Bridge pattern is a good example of the application of the principle of separation of
concerns in this context. It is motivated by the the desire to separate the interface, and
subsequently the implementation of the interface, from an abstraction.
The design resulting from the application of the Bridge design pattern is two orthogonal
class hierarchies that can vary independently.
24.2.1 Identification
Name Classification Strategy
Bridge Structural Delegation
Intent
Decouple an abstraction from its implementation so that the two can vary inde-
pendently ([2]:151)
24.2.2 Structure
2
24.2.3 Participants
Abstraction
Refined Abstraction
Implementor
• defines the interface for implementation classes. This interface doesn’t have
to correspond exactly to Abstraction’s interface; in fact the two interfaces can
be quite different. Typically the Implementor interface provides only primi-
tive operations, and Abstraction defines higher-level operations based on these
primitives.
Concrete Implementor
24.2.4 Problem
A system has a proliferation of classes resulting from a coupled interface and numerous
implementations [4].
24.3.1 Motivation
Suppose you have a ThreadScheduler abstraction that starts off with implementations
for Unix and for Microsoft Windows. For each type of scheduler (for example a time
sliced scheduler, a preemptive scheduler, etc) you need to create two subclasses, one for
each platform of implementation. In fact any subclass of the ThreadScheduler abstraction
needs this further dual implementation. This problem is magnified if you want to make
the ThreadScheduler abstraction work on additional platforms, such as the Macintosh or
a JavaVM. For each existing subclass of ThreadScheduler, an implementation must be
created and added to the inheritance hierarchy. This example from [4] illustrates the lack
of flexibility of this simple inheritance structure shown in Figure 2. The Bridge pattern
offers a solution to avoid this kind of proliferation of classes.
With the Bridge pattern, the ThreadScheduler abstraction becomes easier to implement.
As shown in Figure 3 we have a ThreadScheduler hierarchy of all the abstract scheduler
types on the abstraction side. The implementations of these thread schedulers are pro-
vided by a separate hierarchy of implementations. The link is provided by a reference
to an implementation object, maintained in the ThreadScheduler hierarchy. Thus, any
3
Figure 2: Proliferation of classes to accommodate different schedulers for different plat-
forms
4
Figure 4: Household switch abstraction and implementation hierarchies
Saving on compile-time
Decoupling Abstraction and Implementor eliminates compile-time dependencies on
the implementation. Changing an implementation class doesn’t require recompiling
the Abstraction class and its clients. This property is essential when you must
ensure binary compatibility between different versions of a class library.
Improved Structure
The decoupling between interface and implementation encourages layering that can
lead to a better-structured system. The high-level part of a system only has to know
about Abstraction and Implementor.
5
It’s also possible to delegate the decision to another object altogether. This can typically
be achieved by implementing an abstract factory whose duty is to encapsulate detail
related to the characteristics of the concrete implementors. The factory knows what kind
of Concrete Implementor to create for each situation. An Abstraction simply asks the
abstract factory for an Implementor, and it returns the right kind. A benefit of this
approach is that Abstraction is not coupled directly to any of the Implementor classes.
Strategy
Both Strategy and Bridge use delegation through an abstract interface to concrete
implementations performing operations. However, the operations performed by the
strategy pattern are interchangeable algorithms while the operations performed by
the bridge pattern are common operations acting on interchangeable implementa-
tions such as different data structures or different operating systems.
24.4 Example
6
Participant Entity in application
Abstraction Stack
Refined Abstraction LIFO Stack, HanoiStack
Implementation StackImplementor
Concrete Implementation ArrayImplementor, ListImplementor
operation() push(), pop(), isEmpty(), isFull()
implementation() push(), pop(), isEmpty(), isFull()
Abstraction
• The Stack class defines the abstraction’s interface. It provides the definition of
methods needed to implement any kind of stack. Although the pattern allows
these methods to be concrete, this implementation defines these methods as
virtual to allow the derived classes to override these methods if needed.
• Stack maintains a reference to an object of type Implementor. In this imple-
mentation the reference is established during construction and is not changed
at runtime. To be able to change it at runtime this interface has to define a
setter for stackData.
Refined Abstraction
• HanoiStack and LIFO Stack are refined abstractions. They extend the inter-
face defined by Stack. Since Stack implements the default actions on each
of the operations, which is a simple redirection to the methods in the imple-
mentation with the same name, these derived classes only have to implement
methods that deviate from the default for the specific kind of stack. In this
case LIFO Stack accepts all the default implementations
Implementor
• StackImplementor is the Implementor participant. It defines the interface
for implementation classes. In this case this interface corresponds exactly to
Abstraction’s interface. This is not required for the pattern. In fact the two
interfaces can be quite different. The Stack interface could for example have
added the peek() operation that is defined in HanoiStack without having to
change the Implementor or any of its derivatives.
Concrete Implementor
• ListImplementor and ArrayImplementors are concrete implementations of
the StackImplementor interface and defines its concrete implementation.
24.5 Exercise
1. Change the main program of the given system to create both implementations of
both kinds of stacks.
2. Add another implementation to the given code that uses the <stack> from the C++
STL as an implementation to the given example system.
7
References
[1] Michael Duell. Non-Software Examples of Software Design Patterns. Object Magazine,
7(5):52 – 57, 1997.
[2] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design patterns :
elements of reusable object-oriented software. Addison-Wesley, Reading, Mass, 1995.
[3] Hafedh Mili, Amel Elkharraz, and Hamid Mcheick. Understanding separation of con-
cerns. In Early Aspects: Aspect-Oriented Requirements Engineering and Architecture
Design, 2004.