Patterns: Visitor, Strategy, Chain, State, Command
Total Page:16
File Type:pdf, Size:1020Kb
Software Architecture Bertrand Meyer Lecture 6: More patterns: Visitor, Strategy, Chain, State, Command Command 2 Command pattern - Intent Purpose Way to implement an undo-redo mechanism, e.g. in text editors. [OOSC, p 285-290] ”Way to encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undbldoable operations.” [Gamma et al., p 233] Application example EiffelStudio 3 1 The problem Enabling users of an interactive system to cancel the effect of the last command. Often implemented as “Control-Z”. Should support multi-level undo-redo, with no limitation other than a possible maximum set by the user A good review of O-O techniques 4 Working example: text editor Notion of “current line”. Assume commands such as: ¾ Insert line after current position ¾ Insert line before current position ¾ Delete current line ¾ Replace current line ¾ Swap current line wihith next if any ¾ ... This is a line-oriented view for simplicity, but the discussion applies to more sophisticated views 5 Underlying class (from “business model”) class EDIT_CONTROLLER feature text : LINKED_LIST [STRING] remove -- Remove line at current position. require not off do text.remove end put_right (line: STRING) -- Insert line after current position. require not after do text.put_right (line) end ... end 6 2 Key step in devising a software architecture Finding the right abstractions (Interesting object types) Here: The notion of “command” 7 Keeping the history of the session The history list: Insert Insert RemoveInsert Swap Oldest Most recent history : LINKED_LIST [COMMAND] 8 What’s a “command” object? An instance of COMMAND includes information about one execution of a command by the user, sufficient to: ¾ Execute the command ¾ Cancel the command if requested later For example, in a delete command object, we need: • The position of the line being deleted • The content of that line 9 3 General notion of command deferred class COMMAND feature done: BOOLEAN -- Has this command been executed? execute -- Carry out one execution of this command. deferred ensure: done already: done end undo -- Cancel an earlier execution of this command. require already: done deferred end end 10 Command class hierarchy execute* * undo* COMMAND * deferred + effective + + DELETION INSERTION … + execute+ execute + undo+ undo line index index ... ... 11 A command class (sketch, no contracts) class DELETION inherit COMMAND feature controller : EDIT_CONTROLLER -- Access to business model line : STRING -- Line being deleted index : INTEGER -- Position of line being deleted extxecute -- Remove current line and remember it. do line := controller.item ; index := controller.index controller.remove ; done := True end undo -- Re-insert previously removed line. do controller.go_ith (index) controller.put_left (line) end end 12 4 Executing a user command decode_user_request if “Request normal command” then “Create command object c corresponding to user request” history.extend (c) c.execute elseif “Request UNDO” then if not history.before then -- Ignore excessive requests history. item. undo history.back end Insert Insert Remove Insert elseif “Request REDO” then history.is_last if not then -- Ignore excessive requests item history.forth history.item.execute end end 13 Command pattern: overall architecture history commands * APPLICATION HISTORY COMMAND execute* execute undo* can_undo, can_redo redo* undo, redo undo_all, redo_all extend + + COMMAND_1 COMMAND_2 execute+ execute+ undo+ undo+ redo+ redo+ 14 The undo-redo pattern Has been extensively used Fairly easy to implement Details must be handled carefully (e.g. some commands may not be undoable) Elegant use of O-O techniques Disadvantaggpe: explosion of small classes In Java, can use “inner” classes. 15 5 Using agents For each user command, have two routines: ¾ The routine to do it ¾ The routine to undo it! 16 The history list using agents The history list simply becomes a list of agents pairs: history : LINKED_LIST [TUPLE [PROCEDURE [ANY, TUPLE], PROCEDURE [ANY, TUPLE]] Basic scheme remains the same, but no need for command objects any more; the his tory lis t s imp ly con ta ins agen ts. Insert Insert RemoveInsert Swap De- De- Re-insertDe- Swap insert insert insert 17 Executing a user command (before) decode_user_request if “Request is normal command” then “Create command object c corresponding to user request” history.extend (c) c.execute elseif “Request is UNDO” then if not history.before then -- Ignore excessive requests history item undo . Insert Insert Remove Insert history.back end item elseif “Request is REDO” then if not history.is_last then -- Ignore excessive requests history.forth history. item.execute end end 18 6 Executing a user command (now) “Decode user_request giving two agents do_it and undo_it ” if “Request is normal command” then history.extend ([do_it, undo_it ]) Insert Insert Remove Swap do_it.call ([]) De- De- Re- Swap elseif “Request is UNDO” then insert insert insert if not history.before then history.item.item (2) .call ([]) history.back end elseif “Request is REDO” then if not history.is_last then history.forth history.item.item (1) .call ([]) end 19 end Command - Consequences Command decouples the object that invokes the operation from the one that knows how to perform it. Commands are first-class objects. They can be manipulated and extended like any other object. You can assemble commands into a composite command. It's easy to add new Commands, because you don't have to change existing classes. 20 Command - Participants Command declares an interface for executing an operation. Concrete command ¾ defines a binding between a Receiver object and an action. ¾ implements Execute by invoking the corresponding operation(s) on Receiver. Client creates a ConcreteCommand object and sets its receiver. Invoker asks the command to carry out the request. Receiver knows how to perform the operations associated with carrying out a request. Any class may serve as a Receiver. 21 7 Some design patterns Creational Behavioral ¾ Abstract Factory ¾ Chain of Responsibility ¾ Builder ¾ Command (undo/redo) ¾ Factory Method ¾ Interpreter ¾ Prototype ¾ Iterator ¾ Singleton ¾ Mediator ¾ Memento Structural ¾ Observer ¾ Adapter ¾ State ¾ Bridge ¾ Strategy ¾ Composite ¾ Template Method ¾ Decorator ¾ Visitor ¾ Façade ¾ Flyweight ¾ Proxy Visitor 23 Visitor - Intent “Represents an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.” [Gamma et al., p 331] ¾ Static class hierarchy ¾ Need to perform traversal operations on corresponding data structures ¾ Avoid changing the original class structure 24 8 Visitor application example Set of classes to deal with an Eiffel or Java program (in EiffelStudio, Eclipse ...) Or: Set of classes to deal with XML documents (XML_NODE, XML_DOCUMENT, XML_ELEMENT, XML_ATTRIBUTE, XML_CONTENT…) One parser (or several: keep comments or not…) Many forma tters: ¾ Pretty-print ¾ Compress ¾ Convert to different encoding ¾ Generate documentation ¾ Refactor ¾ … 25 Inheritance hierarchy center * * display* FIGURE rotate* * * OPEN_ perimeter * CLOSED_ FIGURE FIGURE + perimeter + + perimeter SEGMENT POLYLINE POLYGON + ELLIPSE ... ... side1 RECTANGLE side2 TRIANGLE diagonal perimeter ++ * deferred perimeter ++ + effective SQUARE CIRCLE ++ ++ redefined perimeter 26 Polymorphic data structures (POLYGON) (CIRCLE) (CIRCLE) (ELLIPSE) (POLYGON) figs : LIST [FIGURE ] from figs zstart until figs zafter loop figs zitem z display figs zforth end 27 9 The dirty secret of O-O architecture center * * display* FIGURE rotate* Is it easy to add types (e.g. LOZENGE) to existing operations * * OPEN_ perimeter * CLOSED_ FIGURE FIGURE perimeter + perimeter + + SEGMENT POLYLINE POLYGON + ELLIPSE ++ ... ... perimeter side1 RECTANGLE side2 TRIANGLE diagonal ++ perimeter SQUARE CIRCLE perimeter ++ What about the reverse: adding an operation to existing types? Vehicle example * VEHICLE + + TAXI BUS We want to add external functionality, for example: Maintenance Schedule a vehicle for a particular day 29 Vehicle operations Why is this approach bad ? maintain (v : VEHICLE) schedule (v : VEHICLE, d: DAY) -- Perform maintenance on v. -- Set up schedule of v for d. require require exists: v /= Void exists: v /= Void do do if attached {BUS } v as b then if attached {BUS } v as b then b zsend_to_depot b zschedule (d ) end end if attached {TAXI } v as t then if attached {TAXI } v as t then t zsend_to_garage t zput_on_call (d ) (Next_monday) end end ... Other cases ... ... Other cases ... end end end end 30 10 The original view CLIENT visit T_TARGET Client (calls) 31 Visitor participants Target classes Example: BUS, TAXI Client classes Application classes that need to perform operations on target objects Visitor classes Written only to smooth out the collaboration between the other two 32 Visitor participants Visitor General notion of visitor Concrete visitor Specific visit operation, applicable to all target elements Target General notion of visitable element Concrete target Specific visitable element 33 11 The original view CLIENT visit T_TARGET Client (calls) The visitor ballet CLIENT t zaccept (v ) v T_TARGET V_VISITOR v zT_visit (Current ) Client Client (calls) (knows about) Visitor class hierarchies accept* v zT_visit (Current ) * * visit_bus* VISITOR VEHICLE visit_tram* + + + + TAXI BUS MAINTENANCE SCHEDULE _VISITOR _VISITOR accept+ accept+ visit_taxi + visit_taxi