490 likes | 501 Views
Design Patterns. David Talby. This Lecture. Patterns for a User Interface Representing a Document Composite, Flyweight, Decorator Writing Portable Code Abstract Factory, Singleton, Bridge Undo, Macros and Versions Command. A Word Processor.
E N D
Design Patterns David Talby
This Lecture • Patterns for a User Interface • Representing a Document • Composite, Flyweight, Decorator • Writing Portable Code • Abstract Factory, Singleton, Bridge • Undo, Macros and Versions • Command
A Word Processor • Pages, Columns, Lines, Letters, Symbols, Tables, Images, ... • Font and style settings per letter • Frames, Shadows, Background, Hyperlink attached to anything • Unlimited hierarchy: Tables with several Paragraphs containing hyper-linked images inside tables • Should be open for additions...
A Data Structure • First, a uniform interface for simple things that live in a document: class Glyph { void draw(Window *w) = 0; void move(double x, double y) = 0; bool intersects(Point *p) = 0; void insert(Glyph *g, int i) = 0; void remove(int i) = 0; Glyph* child(int i) = 0; Glyph* parent() = 0; }
At Runtime • Unlimited Hierarchy problem solved • Dynamic selection of composites • Open for additions
8. Flyweight • Use sharing to support a large number of small objects efficiently • For example, if every character holds font and style data, a long letter will require huge memory • Even though most letters use the same font and style • How do we make it practical to keep each character as an object?
The Requirements • Reduce the memory demands of having an object per character • Keep the flexibility to customize each character differently
The Solution • Extrinsic state = worth sharing • Intrinsic state = not worth sharing
The Solution II • Put extrinsic state in a class: class CharacterContext { Font* font; bool isItalic, isBold, ...; int size; int asciiCode; // many others… draw(int x, int y) { ... } // other operational methods }
The Solution III • Original class holds rest of state: class Character : public Glyph { CharacterContext *cc; int x, y; draw() { cc->draw(x,y); } }
The Solution IV • A factory manages the shared pool • It adds the object to the pool if it doesn’t exists, and returns it • Here’s Character’s constructor: Character(int x, int y, Font *f, …) { this->x = x; this->y = y; this->cc = factory.createCharacter(f, …); }
The Fine Print • There’s a lot of tradeoff in what is defined as “extrinsic” • Shared pool is usually a hash table • Use reference counting to collect unused flyweights • Don’t rely on object identity • Different objects will seem equal
Known Uses • Word processors • Average 1 flyweight per 400 letters • Widgets • All data except location, value • Strategy design pattern • State design pattern
9. Decorator • Attach additional features to an objects dynamically • For example, many features can be added to any glyph in a document • Background, Note, Hyperlink, Shading, Borders, …
The Requirements • We can freely combine features • An image can have a background,a border, a hyper-link and a note • Features are added and removed dynamically • Can’t afford a class per combination • Should be easy to add new features • Don’t put it all in Glyph
The Solution • Meet Decorator, a class for adding responsibilities to another glyph: class Decorator : public Glyph { void draw() { component->draw(); } // same for other features private: Glyph *component; }
The Solution II • Define concrete decorators: class BackgroundDecorator : public Decorator { void draw() { drawBackground(); glyph->draw(); } }
The Solution III • Many decorators can be added and removed dynamically: • Behavior can be added before and after calling the component • Efficient in space • Order of decoration can matter
The Fine Print • The Decorator class can be omitted if there’s only one decorator or Glyph is very simple • The Glyph class should be lightweight and not store data
Known Uses • Embellishing Document • Background, Border, Note, ... • Communication Streams • Encrypted, Buffered, Compressed
Data Structure Summary • Patterns work nicely together • Composite, Decorator, Flyweight don’t interfere • Data structures are not layered • Instead, clients work on a Glyph interface hiding structures of unknown, dynamic complexity
Saving and Loading • Java has serialization • Save to disk / Send over network by simply writing the root Glyph object of a document • All optimizations saved as well! • Also works on subtrees • Very little coding • In C++ - best to code read() and write() and work the same way
Cut, Copy, Paste • Cut = Detach a subtree • Copy = Clone a subtree • Paste = Attach a subtree • Also works on composite glyphs • Glyphs should hold a reference to parents for the cut operations • Cloning of a flyweight should only increase its reference count!
10. Bridge • Separate an abstraction from its implementations • For example, a program must run on several platforms • Each platform comes with its own set of GUI classes: WinButton, WinScrollBar, WinWindow MotifButton, MotifScrollBar, MotifWindow pmButton, pmScrollBar, pmWindow
Abstract Factory, Right? • An abstract factory can be used to provide these advantages: • Clients treat widgets uniformly • Easy to add or switch families • However, it requires a class per platform per abstraction:
The Requirements • One class per abstraction,one class per implementation • Basically all platforms give similar functionality for a window • If a platform doesn’t support a feature, we should code it once
The Solution • Separate the two hierarchies:
The Solution II • Usually, an implementation inherits the abstraction it implements • The Bridge patterns simply replaces this by composition • This gives several advantages in cases such as the above: • An abstraction can choose its implementation at runtime • Even change implementations
The Solution III • Both the abstractions and the implementations can be extended by subclassing • Changing one does not force recompilation of the other • In C++ this technique completely hides a class’s implementation (its private functions and members) • Many abstractions can share the same implementation
The Fine Print • Choosing the implementor can still be done with an Abstract Factory • Stateless implementors can be shared (so the Abstract Factory becomes a Flyweight factory)
Known Uses • A program that must run on several platforms • A data structures library • Abstractions: Set, List, Array, Table • Implementations: LinkedList, HashTable, DenseArray, … • Different kinds of images versus algorithms to display them
11. Command • Encapsulate commands as objects • We’ll take the the uses one by one: • Undo/Redo • Macros • Queues and logs • Version control • Crash recovery • Message Grouping
The Requirements I • Undo / redo at unlimited depth • Only store relevant data for undo • Easy to add commands
The Solution • Repesent a command as a class: class Command { public: virtual void execute() = 0; virtual void undo() = 0; }
The Solution II • Concrete commands hold undo data: class DeleteLine : public Command { void execute() { line = document->getLine(); document->removeLine(); } void undo() { document->addLine(line); } private: Line line; }
The Solution III • Keep a list of executed commands: Array<Command*> commands; int i; • When you click the ‘Undo’ button: commands(i)->undo(); i--; • When you click the ‘Redo’ button: commands(i)->execute(); i++;
The Solution IV • Whenever a command is activated: commands.add(new_command); i = commands.count(); • When you save a document: document->save(); commands.clear(); i = 0; • The commands list may or may not be limited in size • Only relevant undo data is kept
The Requirements II • Macros are a series of commands • Any command with any of its options may be used • There are also for and while loops, if statements, calls to other macros...
The Solution • A macro is a Composite Command • if, for, while are Decorator Commands
The Requirements III • Commands are accessible from menus as well as toolbars • A command may be available from more than one place • We’d like to configure the menus and toolbars at runtime
The Solution • Each MenuItem or ToolbarItemrefers to its command object • Just as it refers to an image • The command can be configured • Less command classes • Macros fit in the picture as well!
The Requirements IV • Keep multiple versions of a document • When saving, only store the changes from the previous version
The Solution • The changes are exactly the list of commands since the last version was loaded • In addition, a compaction algorithm is needed for commands that cancel each other • Save = Serialize the compacted list • Load = Read early version and call execute on command lists
(More!) Known Uses • Programs log commands to disk so they can be used in case of a crash • Works, since commands are small • Usually in a background thread • Commands can be grouped and sent as one command to a server • Grouping for efficient communication • Grouping to define a transaction • Works even for user defined macros!
Another Summary • Interfaces rule • As long as Command is abstract, who cares how complex it is • Composition rules • Decorator and Bridge provide flexibility in less written code by avoiding inheritance • Serialization should rule