Command Pattern



  • Hallo.

    Ich habe mal ein wenig im Internet gestöbert und das Command pattern gefunden. Es heisst, man kann damit sowas wie Events und Undo-/Redo Operationen programmieren. Allerdings verstehe ich das Prinzip dieses Patterns nicht. Ich steige durch die Schaubilder, die dieses verdeutlichen sollen, einfach nicht durch.

    Kann mir jemand erklären, wie das funktioniert?



  • ich habe für dich ein kleines Beispiel geschrieben, das zeigt, wie man Events, Undo-/Redo Operationen programmieren kann

    #include <stack>
    #include <string>
    #include <iostream>
    
    //abstract command iterface
    struct ICommand
    {
    	//execute action
    	virtual void Do() = 0;
    
    	//undo action
    	virtual void Undo() = 0;
    
    	virtual ~ICommand(){}
    };
    
    //command processor
    class CommandProcessor
    {
    	CommandProcessor()		
    	{		
    	}
    public:
    
    	//singleton von Mayers
    	static CommandProcessor &instance()
    	{
    		static CommandProcessor proc;
    		return proc;
    	}
    
    	//process command
    	void processCommand(ICommand *cmd)
    	{
    		cmd->Do();
    		history.push(cmd);		
    	}
    
    	void redo()
    	{
    		//ich denke, das machst du schon selber
    	}
    
    	//undo last command
    	void undo()
    	{
    		if(!history.empty())
    		{
    			std::auto_ptr<ICommand> cmd(history.top());
    			cmd->Undo();
    			history.pop();
    		}		
    	}
    
    	//can make undo operation
    	bool canUndo() const
    	{
    		return !history.empty();
    	}
    
    	~CommandProcessor()
    	{
    		//clear command history
    		while(!history.empty())
    		{
    			std::auto_ptr<ICommand> cmd(history.top());
    			history.pop();
    		}
    	}
    
    public:	
    	//history of commands
    	std::stack<ICommand*> history;
    };
    
    class Person
    {
    public:
    	void setName(const std::string &name)
    	{
    		this->name = name;
    	}
    
    	const std::string &getName() const
    	{
    		return name;
    	}
    
    private:
    	std::string name;
    };
    
    //command to change a name of person
    class ChangePersonName : public ICommand
    {
    public:
    	ChangePersonName(Person &person, const std::string &newName)
    		:	person(person),
    			name(newName)
    	{		
    	}
    private:
    	std::string name;
    	Person &person;
    private:
    	//ICommand
    	virtual void Do()
    	{
    		//save old name
    		std::string oldName = person.getName();
    
    		//set new name
    		person.setName(name);
    
    		//swap
            name = oldName;		
    	}
    
    	virtual void Undo()
    	{
    		//restore old name
    		person.setName(name);
    	}
    
    };
    
    //persons
    Person p1, p2, p3;
    
    //display names
    void display()
    {
    	std::cout << p1.getName() << " " << p2.getName() << " " << p3.getName() << "\n"; 
    }
    
    int main(int argc, char** argv) 
    {
    	//get command processor
    	CommandProcessor &proc = CommandProcessor::instance();
    
    	std::cout << "init persons\n";
    	//diese drei Operationen gehen nich in historystack
    	p1.setName("Name1");
    	p2.setName("Name2");
    	p3.setName("Name3");
    	display();
    
    	std::cout << "\nchange first name\n";
    	proc.processCommand(new ChangePersonName(p1, "Name1_modified"));
    	display();
    
    	std::cout << "\nchange second name\n";
    	proc.processCommand(new ChangePersonName(p2, "Name2_modified"));
    	display();
    
    	std::cout << "\nundo previos change\n";
    	proc.undo();
    	display();
    
    	std::cout << "\nchange third name\n";	
    	proc.processCommand(new ChangePersonName(p3, "!!!!"));
    	display();
    
    	std::cout << "\nundo all\n"	;
    	while(proc.canUndo())
    	{
    		proc.undo();
    	}
    	display();
    
    	return 0; 
    }
    


  • Hmmm,

    du hast das jetzt mit einem Singleton implementiert. Ist das die einzige Möglichkeit? Ich kann mich erinnern, dass in den Tutorials, die ich so gefunden habe, oftmals Callbacks und Funktionspointer benutzt wurden.

    Gruß Sebastian.



  • ich habe den Singleton nur für CommandProcessor benutzt. ob es für dich richtig oder falsch ist, musstest du selber entscheiden.
    Singleton selbst(genauso wie eine andere Implementirung) hat aber nichts mit dem Pattern "Command" zu tun.
    Man kann Callbacks oder Funktionspointer für diese Sache auch benutzen. ich packte das ganze lieber in eine Klasse (ChangePersonName)



  • ssm schrieb:

    ich packte das ganze lieber in eine Klasse (ChangePersonName)

    Was bei Command durchaus praktisch ist, weil Funktionen ja keinen State haben und man des oefteren etwas fuer das "undo" speichern muss.



  • @ssm
    Danke für den Code.
    Ist ein netter Denkansatz!


Anmelden zum Antworten