260 likes | 281 Views
Lexi case study (Part 2). Presentation by Matt Deckard. User Operations. Support different operations Cut, copy, paste Formatting Printing Etc. Support different Uis Menus Buttons Keyboard shortcuts Etc. User Operations. Avoid coupling operations and UIs
E N D
Lexi case study (Part 2) Presentation by Matt Deckard
User Operations • Support different operations • Cut, copy, paste • Formatting • Printing • Etc. • Support different Uis • Menus • Buttons • Keyboard shortcuts • Etc.
User Operations • Avoid coupling operations and UIs • Use multiple UIs for single operations • Change UIs in the future • Avoid creating dependencies to classes operations are defined in • Undo and Redo • Want some, but not all operations to be undoable • Un-undoable operations: • Saving • Creating new documents • Printing • Don’t want arbitrary limit on levels of undo
User Operations • Encapsulate user operations • GUI menus are just glyphs that perform actions in response to user interaction • Create subclass of Glyph called MenuItem • But don’t want to make each operation a subclass of MenuItem (avoid decoupling!) • Parameterize menu items by uper operations • How?
User Operations • How should user operations be parameterized? • Simple function call has drawbacks: • Doesn’t address undo/redo • Hard to associate state with function • Difficult to extend, reuse • Use objects instead • Can store state, implement undo/redo • Use inheritance to extend, reuse
Command class • Command: abstract class representing user operation • Based on abstract method Execute() • Subclasses will implement Execute() according to the operation they represent • Subclasses can delegate parts of user operation to other objects • Command objects are treated uniformly by requestor • MenuItem will store instance of Command object to encapsulate its associated user operation
Command class • Undoability • Add abstract Unexecute() method • During Execute(), Commands will store whatever information they need to undo later • Add abstract Reversible() method • Returns true if and only if this Command is undoable • Allows Commands to determine undoability at runtime • i.e. redundant or vacuous Commands shouldn’t be undoable • Command history • Need list of Commands to support multiple undos • Traverse back and forth through list to undo and redo • Call Unexecute() when undoing, Execute() when redoing
Command Pattern • Encapsulates a request (user operation) • Prescribes uniform interface for requests • Shields clients from request’s implementation • Allows delegation of request to other objects • Provides centralized access to functionality • Allows to queue or log requests • Supports undo, redo • AKA “Action”, “Transaction” • Similar to functor (but not quite the same)
Command Pattern • Related patterns • Use with Composite and you’ve got: macros! • Use with Memento to remember state (for undo) • Use Prototype to copy command before putting in undo list, to distinguish multiple invocations of same command
Spellchecking and Hyphenation • Textual analysis • Want to support multiple algorithms, addition of new ones in future • Avoid coupling to document structure • Add other types of analysis in future • i.e. search, word count, etc. • Two pieces: • Accessing information to be analyzed • Performing the analysis
Accessing scattered information • Data access • Glyphs may be stored in different ways • Need mechanism to access all of them • Traversal • Different analyses may access Glyphs in different ways (i.e. forward vs. reverse search) • Issues • Only Glyphs know their data structure • Glyph interface shouldn’t be biased toward one structure over another • i.e. using integer index biased us toward arrays • Want to provide multiple access and traversal methods
Accessing scattered information • One approach: adding methods to Glyph • voidFirst(Traversal) – initializes specified traversal • voidNext() – advances to next Glyph in traversal • boolIsDone() – reports if traversal is over • Glyph* GetCurrent() – accesses current glyph • Replaces Child() • voidInsert(Glyph*) – inserts Glyph at current pos • Replaces Insert(Glyph*, int)
Accessing scattered information • Issues with this approach • Must extend Traversal enumeration to support new traversals • Would also have to change lots of subclasses • Difficult to reuse mechanism for other object structures • Doesn’t support multiple traversals in parallel
Iterator class • Better approach: encapsulate access and traversal • Iterator • Abstract class • Defines interface for access and traversal • Subclass contains reference to structure it traverses • CreateIterator() • By default, will return NullIterator • Subclasses can override, based on their structure • Iterators can use CreateIterator() on their root glyphs to support different traversal
Iterator class • Example: PreorderIterator • First() • Calls CreateIterator() on root • Calls First() on returned Iterator • Pushes Iterator onto stack • CurrentItem() • Calls CurrentItem() on Iteratorat top of stack • Returns result • Next() • Calls CreateIterator() on top Iterator • Calls First() on returned Iterator • Pushes Iteratoronto stack • Calls IsDone() on latest Iterator. If true, pops it off and repeats
Iterator Pattern • Abstracts traversal algorithm • Shields clients from internal structure of objects traversed • Gain flexability, usability • Easy to extend • Easy to reuse by parameterizing object type • Can perform multiple traversals in parallel
Performing the analysis • Want to distinguish analysis from traversal • Flexibility, reusability • Different analyses might require same traversal • Want to distinguish different types of glyphs • Different analyses consider different glyphs • Could abstract in Glyph, have subclasses implement • Drawbacks to this approach: • Have to change multiple subclasses • Obscures basic glyph interface
Performing the analysis • Encapsulate the analysis • Create analysis classes • For now, let’s consider a SpellChecker class • Iterator has instance of SpellChecker • Uses SpellChecker instance as it traverses • SpellChecker accumulates information as it is used
Performing the analysis • How to distinguish glyphs? • SpellChecker treats different glyph types differently • Don’t want to resort to type tests or casting (gross) • Instead, have the glyph object tellSpellChecker to check it • Glyph has abstract CheckMe() method • SpellChecker has methods for every glyph subclass, i.e. CheckCharacter(), CheckRow(), CheckImage(), etc. • Glyph sublcasses will override CheckMe() to call the appropriate method in SpellChecker
Performing the analysis • Adding new analyzers • Will be difficult if we define them as separate classes • Instead, abstract it as a Visitor class • CheckMe() becomes the Accept() method • Takes any Visitor as parameter, so don’t have to touch glyph subclasses when adding new analyzers • CheckCharacter(), etc become the Visit() method • Overloaded, takes visited subclass as parameter • Need one for every subclass that implements Accept()
Visitor Pattern • Can be applied to any object structure • Visitees needn’t have common parent class • Tradeoff: • When you add Visitors you don’t have to update object structure, BUT: • When you add subclasses to object structure, you DO have to update your Visitor classes • Sometimes can provide default “do nothing” operation in Visitor • Helps avoid “polluting” classes with many operations • Helps to separate groups of common operations • Can accumulate state as they visit elements • Sometimes compromises encapsulation of visitee • elements
Summary • Needed to support different user operations • Encapsulate the concept that varies (Command) • Needed to support different methods of accessing and traversing data • Encapsulate the concept that varies (Iterator) • Needed to support different analysis algorithms • Encapsulate the concept that varies (Visitor) • (Are we seeing a pattern here?)