behavioral pattern: command c h a p t e r 5 – p a g e 139 there are times when the need arises to...

10
Behavioral Pattern: Command Chapter 5 – Page 1 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.

Upload: egbert-wilcox

Post on 01-Jan-2016

214 views

Category:

Documents


0 download

TRANSCRIPT

Behavioral Pattern: Command

Ch

ap

ter 5

– Pag

e

1

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

Ch

ap

ter 5

– Pag

e

2

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.

Client InvokerCommand

execute()

ConcreteCommand

state

execute()

Receiver

action()

+receiver

execute(): receiver.action()

The Invoker asks the Command to carry out the request.

The Receiver knows how to perform the operations associated with carrying out the request.

Ch

ap

ter 5

– Pag

e

3Non-Software Example: DinerThe 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.

Check

executeOrder()

Waitress

placeOrder()

Customer

order()

Cook

cookOrder()

Order

submitted

executeOrder()

+cook

executeOrder(): cook.cookOrder()

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.

Ch

ap

ter 5

– Pag

e

4Software Example:Integer Conversion

This C++ code will convert an integer into binary, octal, or hexadecimal, but its design is rather rigid.

int num; char ch;char tryAgain = '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') );

Every time the operations change, the switch block needs to be modified.

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.

Ch

ap

ter 5

– Pag

e

5Integer Conversion With The Command PatternBy 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.

IntegerConversion

convertInteger()

User

Integer-BinaryConverter

convert()

IntegerConversionToBinary

convertInteger()

IntegerConversionToOctal

convertInteger()

Integer-OctalConverter

convert()

IntegerConversionToHexadecimal

convertInteger()

Integer-HexadecimalConverter

convert()

Ch

ap

ter 5

– Pag

e

6Command-Based Integer Conversion Code in C++

#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() {}};

class hex_converter : public converter{ public: std::string convert(int i) { std::stringstream ss; ss << std::hex << i; return ss.str(); }};

Ch

ap

ter 5

– Pag

e

7class oct_converter : public converter{ public: std::string convert(int i) { std::stringstream ss; ss << std::oct << i; return ss.str(); }};

class bin_converter : public converter{ public: std::string convert(int i) { std::bitset< sizeof(i) * CHAR_BIT > bits(i); std::stringstream ss; ss << bits; return ss.str(); }};

Ch

ap

ter 5

– Pag

e

8class dictionary{ public: // The dictionary is loaded with the three character/converter pairs dictionary() { dict.insert( std::make_pair( 'B', new bin_converter ) ); dict.insert( std::make_pair( 'O', new oct_converter ) ); dict.insert( std::make_pair( 'H', new hex_converter ) ); }

converter* lookup(char x) { std::map<char, converter*>::const_iterator iter; iter = dict.find( toupper(x) ); if ( iter != dict.end() ) return iter->second; else return NULL; }

~dictionary() { while( dict.begin() != dict.end() ) { delete dict.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;};

Ch

ap

ter 5

– Pag

e

9

void main(){ int num; char ch; char tryAgain = '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

Ch

ap

ter 5

– Pag

e

10

• 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.