Software Design and Architecture
Week 4
A Case Study: Designing a Document Editor - Lexi
Week 2 1
Embellishing the User Interface
● One: A border around the text editing area to demarcate the page
of text.
● Two: Scroll bars that let the user view different parts of the page.
● To make it easy to add and remove these embellishments
(especially at run-time), we shouldn't use inheritance to add
themto the user interface.
● We achieve the most flexibility if other user interface objects don't
even know the embellishments are there. That will let us add and
remove the embellishments without changing other classes.
Week 2 2
Transparent Encolosure
● Embellishing the user interface involves extending existing code.
● Using inheritance stops rearranging embellishments at run-time.
● Inheritance-based approach would cause explosion in the number of
classes.
● We could add a border to Composition by subclassing it to yield a
BorderedComposition class. Or we could add a scrolling interface in
the same way to yield a ScrollableComposition. If we want both scroll
bars and a border, we might produce a
BorderedScrollableComposition, and so forth – Quickly becomes
unworkable.
Week 2 3
Transparent Enclosure
● Object composition offers a potentially more workable and
flexible extension mechanism.
● Since we know we're embellishing an existing glyph, we could
make the embellishment itself an object (say, an instance of
class Border).
● We could have the border contain the glyph, which makes sense
given that the border will surround the glyph on the screen.
● Composing the glyph in the border, keeps the border-drawing
code entirely in the Border class, leaving other classes alone.
Week 2 4
Transparent Enclosure
● The fact that borders have an appearance suggests they should actually be
glyphs; that is, Border should be a subclass of Glyph.
● Clients shouldn't care whether glyphs have borders or not. They should treat
glyphs uniformly.
● When clients tell a plain, unbordered glyph to draw itself, it should do so without
embellishment.
● If that glyph is composed in a border, clients shouldn't have to treat the border
containing the glyph any differently; they just tell it to draw itself as they told the
plain glyph before.
● This implies that the Border interface matches the Glyph interface.
● We subclass Border from Glyph to guarantee this relationship.
Week 2 5
Transparent Enclosure
● All this leads us to the concept of transparent enclosure, which combines the
notions of (1) single-child (or single-component) composition and (2)
compatible interfaces.
● Clients generally can't tell whether they're dealing with the component or its
enclosure (i.e., the child's parent), especially if the enclosure simply
delegates all its operations to its component.
● But the enclosure can also augment the component's behavior by doing
work of its own before and/or after delegating an operation.
● The enclosure can also effectively add state to the component. We'll see
how next.
Week 2 6
Monoglyph
● Apply the concept of transparent enclosure to all glyphs that embellish other
glyphs.
● We'll define a subclass of Glyph called MonoGlyph to serve as an abstract
class for "embellishment glyphs," like Border.
● MonoGlyph stores a reference to a component and forwards all requests to it.
● That makes MonoGlyph totally transparent to clients by default. For example,
MonoGlyph implements the Draw operation like this.
void MonoGlyph::Draw (Window* w) {
_component>Draw(w);
}
Week 2 7
Monoglyph
Week 2 8
Monoglyph
● MonoGlyph subclasses reimplement at least one of these forwarding
operations. Border: : Draw, for instance, first invokes the parent class
operation MonoGlyph: : Draw on the component to let the component do
its part—that is, draw everything but the border. Then Border: : Draw
draws the border by calling a private operation called DrawBorder, the
details of which we'll omit:
void Border::Draw (Window* w) {
MonoGlyph::Draw(w);
DrawBorder(w);
}
Week 2 9
Scroller
● Scroller is a MonoGlyph that draws its component in different
locations based on the positions of two scroll bars, which it adds
as embellishments.
● When Scroller draws its component, it tells the graphics system to
clip to its bounds. Clipping parts of the component that are
scrolled out of view keeps them from appearing on the screen.
● Result: We compose the existing Composition instance in a
Scroller instance to add the scrolling interface, and we compose
that in a Border instance.
Week 2 10
Embellished Object Structure
Week 2 11
Embellishing the User Interface
● Note that we can reverse the order of composition, putting the bordered
composition into the Scroller instance. In that case the border would be
scrolled along with the text, which may or may not be desirable. The
point is, transparent enclosure makes it easy to experiment with
different alternatives, and it keeps clients free of embellishment code.
● Note also how the border composes one glyph, not two or more. This is
unlike compositions we've defined so far, in which parent objects were
allowed to have arbitrarily many children. Here, putting a border around
something implies that "something" is singular.
Week 2 12
Decorator (175)
● The Decorator (175) pattern captures class and object
relationships that support embellishment by transparent enclosure.
● In the Decorator pattern, embellishment refers to anything that
adds responsibilities to an object.
Intent
● Attach additional responsibilities to an object dynamically.
Decorators provide a flexible alternative to subclassing for
extending functionality.
Week 2 13
Decorator (175) - Motivation
● Sometimes we want to add responsibilities to individual objects,
not to an entire class. A graphical user interface toolkit, for
example, should let you add properties like borders or behaviors
like scrolling to any user interface component.
● One way to add responsibilities is with inheritance. Inheriting a
border from another class puts a border around every subclass
instance. This is inflexible, however, because the choice of
border is made statically. A client can't control how and when to
decorate the component with a border.
Week 2 14
Decorator (175) - Motivation
● A more flexible approach is to enclose the component in another object
that adds the border. The enclosing object is called a decorator.
● The decorator conforms to the interface of the component it decorates so
that its presence is transparent to the component's clients.
● The decorator forwards requests to the component and may perform
additional actions (such as drawing a border) before or after forwarding.
● Transparency lets you nest decorators recursively, thereby allowing an
unlimited number of added responsibilities.
Week 2 15
Decorator (175) - Motivation
Week 2 16
Decorator (175) - Motivation
Week 2 17
Decorator (175) - Motivation
Week 2 18
Decorator (175) - Applicability
● to add responsibilities to individual objects dynamically and
transparently, that is, without affecting other objects.
● for responsibilities that can be withdrawn.
● when extension by subclassing is impractical. Sometimes a large
number of independent extensions are possible and would
produce an explosion of subclasses to support every
combination. Or a class definition maybe hidden or otherwise
unavailable for subclassing.
Week 2 19
Decorator (175) - Structure
Week 2 20
Decorator (175) - Participants
● Component (VisualComponent)
– defines the interface for objects that can have responsibilities added to them
dynamically.
● ConcreteComponent (TextView)
– defines an object to which additional responsibilities can be attached.
● Decorator
– maintains a reference to a Component object and defines an interface that
conforms to Component's interface.
● ConcreteDecorator (BorderDecorator, ScrollDecorator)
– adds responsibilities to the component.
Week 2 21
Decorator (175) - Collaborations
● Decorator forwards requests to its Component object. It may
optionally perform additional operations before and after
forwarding the request.
Week 2 22
Decorator (175) - Consequences
More flexibility than static inheritance.
● The Decorator pattern provides a more flexible way to add responsibilities
to objects than can be had with static (multiple) inheritance. With
decorators, responsibilities can be added and removed at run-time simply
by attaching and detaching them. In contrast, inheritance requires
creating a new class for each additional responsibility (e.g.,
BorderedScrollableTextView, BorderedTextView).
● Decorators also make it easy to add a property twice. For example, to
give a Text View a double border, simply attach two BorderDecorators.
Inheriting from a Border class twice is error-prone at best.
Week 2 23
Decorator (175) - Consequences
Avoids feature-laden classes high up in the hierarchy.
● Decorator offers a pay-as-you-go approach to adding responsibilities.
Instead of trying to support all foreseeable features in a complex,
customizable class, you can define a simple class and add
functionality incrementally with Decorator objects.
● Functionality can be composed from simple pieces. As a result, an
application needn't pay for features it doesn't use.
● It's also easy to define new kinds of Decorators independently from
the classes of objects they extend
Week 2 24
Decorator (175) - Consequences
A decorator and its component aren't identical.
● A decorator acts as a transparent enclosure. But from an object
identity point of view, a decorated component is not identical to
the component itself. Hence you shouldn't rely on object identity
when you use decorators.
Week 2 25
Decorator (175) - Consequences
Lots of little objects.
● A design that uses Decorator often results in systems composed
of lots of little objects that all look alike.
● The objects differ only in the way they are interconnected, not in
their class or in the value of their variables.
● Although these systems are easy to customize by those who
understand them, they can be hard to learn and debug.
Week 2 26
Decorator (175) - Implementation
● Interface conformance. A decorator object's interface must conform
to the interface of the component it decorates. ConcreteDecorator
classes must therefore inherit from a common class (at least in C++).
● Omitting the abstract Decorator class. There's no need to define
an abstract Decorator class when you only need to add one
responsibility. That's often the case when you're dealing with an
existing class hierarchy rather than designing a new one. In that
case, you can merge Decorator's responsibility for forwarding
requests to the component into the ConcreteDecorator.
Week 2 27
Decorator (175) - Implementation
● Keeping Component classes lightweight. To ensure a
conforming interface, components and decorators must descend
from a common Component class. It's important to keep this
common class lightweight; that is, it should focus on defining an
interface, not on storing data. The definition of the data
representation should be deferred to subclasses; otherwise the
complexity of the Component class might make the decorators
too heavyweight to use in quantity.
Week 2 28
Decorator (175) - Implementation
● Changing the skin of an object versus changing its guts. We can think of a decorator
as a skin over an object that changes its behavior. An alternative is to change the object's
guts. The Strategy (315) pattern is a good example of a pattern for changing the guts.
● Strategies are a better choice in situations where the Component class is intrinsically
heavyweight, thereby making the Decorator pattern too costly to apply. In the Strategy
pattern, the component forwards some of its behavior to a separate strategy object. The
Strategy pattern lets us alter or extend the component's functionality by replacing the
strategy object.
● For example, we can support different border styles by having the component defer
border-drawing to a separate Border object. The Border object is a Strategy object that
encapsulates a border-drawing strategy. By extending the number of strategies from just
one to an open-ended list, we achieve the same effect as nesting decorators recursively.
Week 2 29
Decorator (175) - Implementation
● Since the Decorator pattern only changes a component from the
outside, the component doesn't have to know anything about its
decorators; that is, the decorators are transparent to the
component:
Week 2 30
Decorator (175) - Implementation
● With strategies, the component itself knows about possible
extensions. So it has to reference and maintain the
corresponding strategies:
Week 2 31
Decorator (175) - Implementation
● The Strategy-based approach might require modifying the
component to accommodate new extensions. On the other hand,
a strategy can have its own specialized interface, whereas a
decorator's interface must conform to the component's.
Week 2 32
Decorator (175) – Sample Code
class VisualComponent {
public:
VisualComponent();
virtual void Draw();
virtual void Resize();
// . . .
};
Week 2 33
Decorator (175) – Sample Code
● Decorator decorates the VisualComponent referenced by the -component
instance variable, which is initialized in the constructor. For each operation
in VisualComponent's interface, Decorator defines a default implementation
that passes the request on to -component:
void Decorator::Draw () {
_component>Draw();
}
void Decorator::Resize () {
_component>Resize();
}
Week 2 34
Decorator (175) – Sample Code
class BorderDecorator : public Decorator {
public:
BorderDecorator(VisualComponent*, int borderWidth);
virtual void Draw();
private:
void DrawBorder(int);
private:
int _width;
};
void BorderDecorator::Draw () {
Decorator::Draw();
DrawBorder(_width);
}
Week 2 35
Decorator (175) – Sample Code
● A similar implementation would follow for ScrollDecorator and
DropShadowDecorator, which would add scrolling and drop
shadow capabilities to a visual component.
Week 2 36
Decorator (175) – Sample Code
● Now we can compose instances of these classes to provide different
decorations. The following code illustrates how we can use decorators to
create a bordered scrollable Text View.
● First, we need a way to put a visual component into a window object.
We'll assume our Window class provides a SetContents operation for
this purpose:
void Window::SetContents (VisualComponent* contents) {
// . .
}
Week 2 37
Decorator (175) – Sample Code
● Now we can create the text view and a window to put it in:
Window* window = new Window;
TextView* textView = new TextView;
● TextView is a VisualComponent, which lets us put it into the window:
window>SetContents(textView);
● But we want a bordered and scrollable TextView. So we decorate it
accordingly before putting it in the window.
window→SetContents( new BorderDecorator( new
ScrollDecorator(textView), 1 ) );
Week 2 38