100 likes | 255 Vues
Behavioral Pattern: Command. Chapter 5 – Page 139. There are times when the need arises to issue a request to an object without knowing anything about the operation being requested or the object receiving the request.
E N D
Behavioral Pattern: Command Chapter 5 – Page 139 There are times when the need arises to issue a request to an object without knowing anything about the operation being requested or the object receiving the request. The Command Pattern encapsulates the request as an object, which allows clients to be parameterized with different requests, requests to be logged or queued, and undoable operations to be supported.
The Command Pattern Chapter 5 – Page 140 The Command declares an interface for executing an operation. The ConcreteCommand defines a binding between a Receiver object and an action, implementing Execute by invoking the corresponding operation on the Receiver. The Client creates a ConcreteCommand object and sets its Receiver. The Invoker asks the Command to carry out the request. The Receiver knows how to perform the operations associated with carrying out the request.
Non-Software Example: Diner Chapter 5 – Page 141 The Waitress (invoker) generates an Order (concrete command) based upon what the Customer (client) selects from the menu, placing the order on a Check (command). The Order is then queued for the Cook (receiver) and, after being executed, the Order is delivered to the Customer. Note that the pad of “checks” used by each waitress is not dependent on the menu, so they can support commands to cook many different items.
Software Example: Integer Conversion Chapter 5 – Page 142 This C++ code will convert an integer into binary, octal, or hexadecimal, but its design is rather rigid. Every time the operations change, the switch block needs to be modified. int num; charch; chartryAgain = 'y'; string output; do { cout << "Enter an integer value: "; cin >> num; cout << "Enter a conversion type " << " (b=binary, o=octal, h-hexadecimal): "; cin >> ch; switch( toupper(x) ) {case 'O': { output = to_oct(num); break; }case 'H': { output = to_hex(num); break; }case 'B': { output = to_bin(num); break; }default: { output = "invalid"; } } cout << "Result: " << output << endl << endl; cout << "Try again? (Y or N) "; cin >> tryAgain; } while ( (tryAgain == 'Y') || (tryAgain == 'y') ); At the moment, operations are just functions which exist completely independently of each another, even though they're all in fact fairly similar, and selection is solely based on different character values. (These character values acting like runtime identifiers for the operations.) A better solution would build in some kind of relationship between all the operations, and tag each one with their character value identifier.
Integer Conversion With The Command Pattern Chapter 5 – Page 143 By separating the conversion command from the object that performs it (the convertor), the total amount of code will undoubtedly increase, but the revised design breaks bigger problems down into sub-problems, producing small, reusable modules in the process.
Command-Based Integer Conversion Code in C++ Chapter 5 – Page 144 #include <iostream> #include <string> #include <bitset>// Enables the determination of bit lengths #include <sstream>// Enables the creation/loading of string-streams #include <map>// Enables the mapping of dictionary pairs using namespace std; class converter { public: virtual std::string convert(int) = 0; virtual ~converter() {} }; classhex_converter : public converter { public: std::string convert(inti) { std::stringstreamss; ss << std::hex << i; return ss.str(); } };
Chapter 5 – Page 145 classoct_converter : public converter { public: std::string convert(inti) { std::stringstreamss; ss << std::oct << i; return ss.str(); } }; classbin_converter : public converter { public: std::string convert(inti) { std::bitset< sizeof(i) * CHAR_BIT > bits(i); std::stringstreamss; ss << bits; return ss.str(); } };
class dictionary { public: // The dictionary is loaded with the three character/converter pairs dictionary() { dict.insert( std::make_pair( 'B', newbin_converter ) ); dict.insert( std::make_pair( 'O', newoct_converter ) ); dict.insert( std::make_pair( 'H', newhex_converter ) ); } converter* lookup(char x) { std::map<char, converter*>::const_iteratoriter; iter = dict.find( toupper(x) ); if ( iter != dict.end() ) returniter->second; else return NULL; } ~dictionary() { while( dict.begin() != dict.end() ) { deletedict.begin()->second; dict.erase( dict.begin() ); } } private: // The STL <map> library enables mapping between objects // between two types. In this case, the three user-entered // characters have corresponding converters, and this // mapping is used to create a dictionary between them. std::map<char, converter*> dict; }; Chapter 5 – Page 146
Chapter 5 – Page 147 void main() { int num; charch; chartryAgain = 'y'; string output; dictionary dict; do { cout << "Enter an integer value: "; cin >> num; cout << "Enter a conversion type (b=binary, o=octal, h-hexadecimal): "; cin >> ch; converter* con = dict.lookup( ch ); if ( con != NULL ) output = con->convert( num ); else output = "Invalid"; cout << "Result: " << output << endl << endl; cout << "Try again? (Y or N) "; cin >> tryAgain; } while ( (tryAgain == 'Y') || (tryAgain == 'y') ); }
Command Pattern Advantages Chapter 5 – Page 148 • The object that creates a command (the Client) is not the same object that executes it (the Invoker). This separation provides flexibility in the timing and sequencing of commands. • Materializing commands as objects means they can be passed, staged, shared, loaded in a table, and otherwise instrumented or manipulated like any other object. • Command objects can be thought of as "tokens" that are created by one entity that knows what needs to be done, and passed to another entity that has the resources for doing it. • Another of the main reasons for using the Command Pattern is that it provides a convenient way to store and execute an Undo function. Each command object can remember what it just did and restore that state when requested to do so if the computational and memory requirements are not too overwhelming.