Design Patterns - Case Study LEXI
Design Patterns - Case Study LEXI
Case Study:
Designing A
Document Editor
Tech Lunch
Bill Kidwell
2
Lexi
Features
•WYSIWYG Document Editor
•Mix text and graphics in a variety
of styles
•pull-down menus
•scrollbars
3
Design Problems
Quick: We will explore each
1. Document Structure
How do we represent a document?
2. Formatting
How do we arrange text and graphics on the screen (or paper)
3. Embellishing the user interface
4. Supporting multiple look-and-feel standards
5. Supporting multiple window systems
6. User Operations
7. Spelling checking and hyphenation
4
Document Structure
5
Design Issue #1:
Document Structure
•Documents are really just a combination of characters,
lines, polygons, etc.
•Often a user will want to deal with things at a higher
level (ex. a picture or a row or column of a document)
•To make Lexi user-friendly, we need to allow the user
to deal with these higher level constructs
6
Design Issue #1:
Document Structure
•The internal representation of the document structure
should match the physical structure
•Allowarrangement of text and graphics into lines,
columns, tables, etc.
•Need to draw the document on the screen
•Needto map mouse clicks to specific parts of the
document to be handled at the right level
7
Document Structure
8
Design Issue #1:
Document Structure
9
Design Issue #1:
Document Structure
Recursive Composition
•A method for representing a hierarchy of information
•A grouping of simple items to create a composite item
•Groupings of items can be a part of even higher level groups
10
Design Issue #1:
Document Structure
• Implications
– Objects need corresponding classes
– All of these classes need compatible interfaces
• Allows us to treat them uniformly
• Meet the Glyph
Responsibility Operations
virtual void Draw(Window*)
Appearance virtual void Bounds(Rect&)
virtual bool Intersects(const Point&)
Hit Detection
virtual boid Insert(Glyph*, int)
Structure virtual void Remove(Glyph*)
virtual Glyph* Child(int)
virutal Glyph* Parent()
11
Design Issue #1:
Document Structure
12
Design Issue #1:
Document Structure
• Recursive Composition – It’s not just for documents
• Useful for any potentially complex, hierarchical structure
13
The Composite Pattern
Defines behavior
For the primitives
-Behavior for composites
-Stores Child components
14
Design Issue #2
Formatting
• How to construct a particular physical structure
• And maintain separation of data and view (format)
• Properly formatted document
• Some possible responsibilities:
• Break text into lines
• Break lines into columns
• Margin Widths
• Indentation
• Tabulation
• Single/Double Spacing
• Authors restrict the example to breaking glyphs into lines
15
Formatting
16
Design Issue #2
Formatting
• Important Trade-Off
– Formatting quality vs. formatting speed
– Formatting speed vs. Storage requirements
• It will be complex… so our goals:
– Keep it well-contained
– Independent of document structure
• Add a new glyph… not have to worry about changing format code
• Add new formatting algorithm – not have to change glyphs
17
Design Issue #2
Formatting
• Needs to be easy to change the formatting algorithm
– If not at run-time, at least at compile-time
• We can make it independent, self contained and replaceable by putting it in its
own class
• We can make it run-time replaceable by creating a class hierarchy for
formatting algorithms
• Compositor
Responsibility Operations
18
Design Issue #2
Formatting
19
Design Issue #2
Formatting
• Rows and Columns are inserted by the compositor
• Why rows and columns?
– Inserted by the line-breaking algorithm
20
Design Issue #2
Formatting
• Why do we need different Compositor’s?
– In the Example:
• SimpleCompositor might do a quick pass without regard for such
esoterica as the document's "color." Good color means having an even
distribution of text and whitespace.
• A TeXCompositor would implement the full TeX algorithm [Knu84],
which takes things like color into account in exchange for longer
formatting times.
• Compositor-Composition class split ensures a strong separation
between code that supports the document's physical structure and
the code for different formatting algorithms
• We can change the linebreaking algorithm at run-time by adding a
single SetCompositor operation to Composition's basic glyph
interface.
21
Design Issue #2
Formatting
• Have we seen this before?
– Encapsulating an algorithm in an object is the intent of the Strategy
(315) pattern.
– Key participants in the pattern are
• Strategy objects (Compositors)
• Context object (Composition)
– The key to using Strategy
• Interfaces for the strategy and the context that will support a range of
algorithms
• Ideally we don’t want to change these interfaces to support a new
algorithm
22
Design Issue #3
Embellishing the user interface
• Two Embellishments
– Add a Border around the text editing area
– Add scroll bars
23
Design Issue #3
Embellishing the User Interface
• Basically, we want to extend the code to provide a Transparent
Enclosure
– Transparent in that the page itself does not know anything about the
changes – it behaves the same
• How should we do this?
– We could use Inheritance, how would that look?
– We have a Composition class…
• To add a Border we add a BorderedComposition
• To add a Scroll bar we add a ScrollableComposition
• What about both? BorderedScrollableComposition?
• How could we do it with object composition instead?
– What object “has” what object?
– How do we make it extensible?
24
Design Issue #3
Embellishing the User Interface
• This is an example of the Decorator Pattern
• The authors call it the “MonoGlyph”
// I pass the buck…
void MonoGlyph::Draw (Window* w)
{ _component->Draw(w); }
// Extend Draw
void Border::Draw (Window* w)
{
MonoGlyph::Draw(w);
DrawBorder(w);
}
Multiple Embellishments….
25
Decorator
Related Patterns
• Adapter (139): A decorator is different from an adapter in that a
decorator only changes an object's responsibilities, not its
interface; an adapter will give an object a completely new
interface.
• Composite (163): A decorator can be viewed as a degenerate
composite with only one component. However, a decorator adds
additional responsibilities—it isn't intended for object
aggregation.
• Strategy (315): A decorator lets you change the skin of an object;
a strategy lets you change the guts. These are two alternative ways
of changing an object.
26
Design Issue #4
Supporting Multiple Look-and-Feel Standards
• One major problem in portability… consider look-and-feel for
– Windows
– Max OS X
– KDE
• If re-targeting is too difficult (expensive), it won’t happen
• NOTE: Just one of the issues… Look-and-Feel … we deal with
the Windowing system itself next
• We use an Abstract Factory Pattern
• This allows us to define the product type at compile time or run-
time (based on environment or user input)
27
Design Issue #4
Supporting Multiple Look-and-Feel Standards
// Creating a scrollbar…
ScrollBar* sb = guiFactory->CreateScrollBar();
28
Design Issue #4
Supporting Multiple Look-and-Feel Standards
29
Abstract Factory
Related Patterns
• AbstractFactory classes are often implemented with factory
methods (Factory Method (107)), but they can also be
implemented using Prototype (117) [Creation by Cloning].
30
Design Issue #5
Supporting Multiple Window Systems
• What about the Windowing System itself?
• The APIs differ… not just the visual elements
– Can we use Abstract Factory?
• Not easily… vendors already define class hierarchies
• How do we make classes from different hierarchies comply to the same
abstract type?
– We use Bridge to
• define a uniform set of windowing abstractions (common interface)
• Hide the individual implementations
31
Design Issue #5
Supporting Multiple Window Systems
• Common things a Window class must do (responsibilities)
– Provide operations for drawing basic geometric shapes
– Maximize/Minimize
– Resize
– (re)draw contents on demand (when restored, overlapped, obscured,
etc…)
• Two Possible Philosophies (Extremes)
– Intersection of functionality – Only define what is common to all
– Union of functionality – Incorporate capabilities of all systems
32
Design Issue #5
Supporting Multiple Window Systems
• We adopt a hybrid
Responsibility Operations
window management virtual void Redraw()
virtual void Raise()
virtual void Lower()
virtual void Maximize()
virtual void Minimize()
graphics virtual void DrawLine()
virutal void DrawRect()
virtual void DrawPolygon()
Virtual void DrawText()
33
Design Issue #5
Supporting Multiple Window Systems
34
Design Issue #5
Supporting Multiple Window Systems
35
Bridge Pattern
Related Patterns
• An Abstract Factory (87) can create and configure a particular
Bridge
• The Adapter (139) pattern is geared toward making unrelated
classes work together. It is usually applied to systems after they're
designed. Bridge, on the other hand, is used up-front in a design to
let abstractions and implementations vary independently.
36
Design Issue #6
User Operations
• Possible Operations
– Creating a new document
– Open, save, print a document
– Cut and Paste
– Format text
• We have different interfaces for these operations
– Different Look-and-Feel
– Different Windowing Systems
– Different Access Points (menu, shortcut key, context menu)
• We want independence from the UI
– UI triggers the action, but I don’t depend on the UI
37
Design Issue #6
User Operations
• Furthermore…
– The operations are implemented in many different classes
– We want to access the functionality without adding dependency
between the UI classes and all of the different classes involved
• That’s not all
– We also want to support undo and redo for some functionality
• We need to encapsulate the request using the Command Pattern
38
Design Issue #6
User Operations
39
Design Issue #6
User Operations
• Each MenuItem can store an appropriate command
40
Design Issue #6
User Operations
• What about Undo? We add an Unexecute() method and keep a
command history…
UNDO REDO
41
Command Pattern
Related Patterns
• A Composite (163) can be used to implement MacroCommands.
• A Memento (283) can keep state the command requires to undo its
effect.
• A command that must be copied before being placed on the
history list acts as a Prototype (117).
42
Design Issue #7
Spell Check and Hyphenation
• Similar constraints to formatting
• Need to support multiple algorithms
• We may want to add
– search
– grammar check
– word count
• This is too much for any single pattern…
• There are actually two parts
– (1) Access the information
– (2) Do the analysis
43
Design Issue #7
Spell Check and Hyphenation – Accessing the Information
• We can encapsulate access and traversal using the Iterator Pattern
Methods
void First(Traversal kind)
void Next()
bool IsDone()
Glyph* GetCurrent()
void Insert(Glyph*)
44
Design Issue #7
Spell Check and Hyphenation – Accessing the Information
• Using the Iterator to do our analysis…
• An example
Glyph* g;
for (g->First(PREORDER);
!g->IsDone();
g->Next())
{
Glyph* current = g->GetCurrent();
// do some analysis
}
45
Design Issue #7
Spell Check and Hyphenation – The Analysis
• We don’t want our analysis in our iterator
– Iterators can be reused
• We don’t want analysis in our Glyph class
– Every time we add a new type of analysis… we have to change our
glyph classes
• Therefore
– Analysis gets its own class
– It will use the appropriate iterator
– Analyzer class accumulates data to analyze as it goes
46
Design Issue #7
Spell Check and Hyphenation – The Analysis
• We don’t want…
void SpellingChecker::Check (Glyph* glyph)
{
Character* c; HARD TO EXTEND
Row* r;
Image* i;
if (c = dynamic_cast<Character*>(glyph))
HAVE TO CHANGE WHEN
{
// analyze the character WE CHANGE GLYPH HIERARCHY
}
else if (r = dynamic_cast<Row*>(glyph))
{
// prepare to analyze r's children
}
else if (i = dynamic_cast<Image*>(glyph))
{ // do nothing }
}
47
Design Issue #7
Spell Check and Hyphenation – The Analysis
• Instead… we use the Visitor Pattern
class Visitor
{
public:
virtual void VisitCharacter(Character*) { }
virtual void VisitRow(Row*) { }
virtual void VisitImage(Image*) { }
// ... and so forth
};
• Then, we can define
– SpellCheckingVisitor
– HyphenationVisitor
• Within Glyph we define an operation
– void VisitMe(Visitor& visitor)
• Character class would call visitor.VisitCharacter(this)
• Row class would call visitor.VisitRow(this)
48
Visitor Pattern
Related Patterns
• Composite (163): Visitors can be used to apply an operation over
an object structure defined by the Composite pattern.
• Interpreter (243): Visitor may be applied to do the interpretation.
– Embedding of a domain specific language or scripting language
within an application
49
Next Week
50
Thank You