Design Patterns David Talby
This Lecture n More for a Document Editor u Synchronizing Multiple Windows F Observer u Simplifying complex interactions F Mediator u Working on complex structures F Iterator F Visitor
12. Observer n Define a one-to-many dependency between objects, so that changing one automatically updates others n For example, a spreadsheet and several charts of it are open n Changing data in a window should be immediately reflected in all
The Requirements n Document and Chart classes must not know each other, for reuse n Easily add new kinds of charts or other links n A dynamic number of charts
The Solution n Terminology u Subject and Observer u Publisher and Subscriber u Listeners n Subjects attach and detach listeners, and notify of events n Clients update themselves after receiving a notification
The Solution II n Here’s an abstract observer: class Observer { void update() = 0; } n Concrete observers such as class Chart will inherit Observer
The Solution III n Here’s the (concrete!) subject: class Subject { void attach(Observer *o) { observers.add(o); } void detach(Observer *o) { observers.remove(o); } void notify() { for i in observers do o->update(); } protected: List observers; }
The Solution IV n Both subject and observer will usually inherit from other classes as well n If multiple inheritance is not available, the observer must be a separate class that has a reference to the chart object and updates it n Java has a special mechanism – Inner classes – to make this easier
The UML
The Fine Print n Observing more than one subject u Update must include an extra argument to tell who is updating n Observing only certain events u Attach must include an extra argument to tell which events interest this observer n Observing small changes u Update includes arguments to tell what changed, for efficiency
The Fine Print II n Who calls Notify ? u Greedy – the subjects, on change u Lazy – the observers, on query n Common errors u Forgetting to detach an object when it is destroyed u Calling Notify in an inconsistent state n Java includes Observer as part of the standard libraries u In package java.util
Known Uses n All frameworks of all kinds u MFC, COM, Java, EJB, MVC, … n Handle user interface events n Handle asynchronous messages
13. Mediator n Encapsulate a complex interaction to preserve loose coupling n Prevent many inter-connections between classes, which means that changing their behavior requires subclassing all of them n For example, a dialog box includes many interactions of its widgets. How do we reuse the widgets?
The Requirements n A widget is a kind of colleague n Colleague don’t know about the interactions they participate in u Can be reused for different dialogs n Colleagues don’t know about others u Allow only O(n) connections n Easy to change interactions
The Solution n All colleagues talk with a mediator n The mediator knows all colleagues n Whenever a colleague changes, it notifies its mediator n The mediator codes the interaction logic, and calls operations on other colleagues
The Solution II n An example interaction:
The Solution III n Only O(n) connections:
The UML
The Fine Print n The interaction logic (mediator) and colleagues can be reused separately and subclassed separately n Protocols are simpler since n-to-1 relations replace n-to-m relations n Abstract mediator class is unnecessary if there’s only one mediator n Observer or mediator? u One-to-many or many-to-many? u Should the logic be centralized?
Known Uses n Widgets in a user interface u Delphi and VB “hide” this pattern n Connectivity constraints in diagrams
14. Iterator n without exposing its representation n An extremely common pattern n For example, a list should support forward and backward traversals u Certainly not by exposing its internal data structure n Adding traversal methods to List ’s interface is a bad idea
The Requirements n Traversal operations should be separate from List ’s interface n Allow several ongoing traversals on the same container n Reuse: it should be possible to write algorithms such as findItem that work on any kind of list
The Solution n Define an abstract iterator class: class Iterator { void first() = 0; void next() = 0; bool isDone() = 0; G* item() = 0; }
The Solution II n Each data structure implementation will also implement an iterator class: u ListIterator u HashTableIterator u FileIterator u StringIterator n Each data structure can offer more than one iterator: u Forward and backward iterators u Preorder, inorder, postorder
The Solution III n For example: class BackwardArrayIterator : public Iterator { Array *container; int pos; public: BackwardArrayIterator(Array *a) { container = a; first(); } next() { --pos; } // other methods easy }
The Solution IV n A data structure’s interface should return iterators on itself: class List { Iterator * getForwardIterator() { return new ListForwardIterator(this); } Iterator * getBackwardIterator () // similarly } n Now every LinkedList object can have many active iterators
The Solution V n Writing functions for containers: void print(Iterator * it) { for (it->first(); !it->isOver(); it->next()) cout item(); } n Using them: print(myList->getBackwardIterator()); print(myTable->getColumnItr(“Age”)); print(myTree->getPostOrderIterator());
The Solution VI n Generic algorithms can be written: G* findItem(Iterator * it, G *element) { while (!it->isOver()) { if (it->item() == element) return element; it->next(); } return NULL; }
The Requirements II n Some iterators are generic: u Traverse every n’th item u Traverse items that pass a filter u Traverse only first n items u Traverse a computed view of items n Such iterators should be coded once n It should be easy to combine such iterators and add new ones n Their use should be transparent
The Solution n Use the Decorator design pattern n For example, FilteredIterator receives another iterator and the filtering function in its constructor n It delegates all calls to its internal iterator except first() and next(): void next() { do it->next() while (!filter(it->item() && !it->isOver()); }
The Solution II n It is then easy to combine such generic iterators n Print square roots of the first 100 positive elements in a list: print(new LimitedIterator(100, new ComputedIterator(sqrt, new FilteredIterator(positive, list->getForwardIterator())))); n Adding an abstract DecoratorIterator reduces code size if many exist
The UML
The Fine Print n Everything is a container u Character strings u Files, both text and records u Socket streams over the net u The result of a database query u The bits of an integer u Stream of random or prime numbers n This allows reusing the print, find and other algorithms for all of these
The Fine Print II n Iterators may have privileged access u They can encapsulate security rights n Kinds of abstract iterators u Direct access iterators u Access the previous item n Robustness issues u Is the iterator valid after insertions or removals from the container? n Iterators and the Composite pattern
Known Uses n All major standard libraries of popular programming languages u STL for C++ u The new Java containers n New libraries for file, network and database access in C++ conform to STL’s iterators as well
15. Visitor n Separate complex algorithms on a complex data structure from the structure’s representation n For example, a document is a composite structure involved in many complex operations u Spell check, grammar check, hyphenation, auto-format, … n How do we avoid cluttering Glyph subclasses with all this code?
The Requirements n Encapsulate complex algorithms and their data in one place n Outside the data structure n Easily support different behavior for every kind of Glyph n Easily add new tools
The Solution n Say hello to class Visitor : class Visitor { public: void visitImage(Image *i) { } void visitRow(Row *r) { } void visitTable(Table *t) { } // so on for every Glyph type } n Every tool is a subclass: class SpellChecker : public Visitor
The Solution II n Add to Glyph ’s interface the ability to accept visitors: void accept(Visitor *v) = 0; n Every glyph subclass accepts a visitor by an appropriate callback: class Image : public Glyph { void accept(Visitor *v) { v->visitImage(this); } n This way the visitor is activated for the right kind of glyph, with its data
The Solution III n Initiating a spell check (one option): u Create a SpellChecker object root->accept(sc); n Graphic non-text glyphs will just ignore the visit u This is why Visitor includes default empty method implementations n Composite glyphs also do nothing u They can forward the visit to all their children. This can be coded once in CompositeGlyph
The Solution IV n Easy to add operations u Word count on characters u Filters such as sharpen on images u Page layout changes on pages n Works on any glyph u In particular, a dynamic selection as long as it’s a composite glyph n Adding a tool does not require recompilation of Glyph hierarchy
The UML
The Fine Print n The big problem: adding new Glyph subclasses is hard u Requires small addition of Visitor, and recompilation of all its subclasses n How do we traverse the structure? u Using an iterator u From inside the accept() code u From inside the visitxxx() code n Visitors are really just a workaround due to the lack of double dispatch
Known Uses n Document Editors u Spell Check, Auto-Format, … n Photo Editors u Filters & Effects n Compilers u Code production, pretty printing, tests, metrics and optimizations on the syntax tree
Summary n Pattern of patterns u Encapsulate the varying aspect u Interfaces u Inheritance describes variants u Composition allows a dynamic choice between variants n Design patterns are old, well known and thoroughly tested ideas u Over twenty years!