Command / Command Design Pattern

    Read the description of other patterns.
    A

    Problem


    It is necessary to have an effective representation of queries to a certain system, while not possessing knowledge of either their nature or methods of processing them.

    Description


    There are at least three motivations for using the “Team” template:
    • encapsulating the request as an object for subsequent logging / logging, etc.
    • endowing the essence of “calling the method of an object” with the properties of an independent object;
    • object oriented callback (callback);


      In fact, all of the above motivations come down to one - “encapsulating the request as an object”. A large number of types of systems are known where such an approach is effective and justified. Firstly, it’s any less serious desktop application with the ability to cancel and repeat user actions (undo / redo). Secondly, these are network distributed systems using queries in the form of objects as the main primitive of initializing some operations. Thirdly, systems with support for asynchronous calls encapsulating a callback in the form of a callback object. You can list endlessly, it is important to understand that the command template is one of the most common templates, which, you can say, appeared literally at the stage of the formation of OOP and only then was formalized and described in the GoF book.

      The fundamental idea of ​​the template is to use a single interface to describe all types of operations that can be performed with the system. Then, to add a new operation to the support system, it is enough to implement the proposed interface. Thus, each operation appears to be an independent object encapsulating a certain set of additional properties. The system, in turn, acquires the ability to perform an additional set of actions on requests (objects). This is logging, undoing the previous action, repeating the next one, etc.

      Practical example


      Consider the most obvious motivation for using the template - logging / undo-redo. To do this, imagine that it was,
      if Stephen Bourne (author of bash) read the GoF book and knew what the “Team” design pattern is
      . It would be approximately the following:
      • each command in the shell would be encapsulated in a separate class - a descendant of the Command class;
      • the command shell would support logging / logging mechanisms and, most importantly, undo / redo user actions, i.e. teams;


      We will try to change the problems in the design of existing solutions and write your bike went with the ability to cancel and repeat the commands .

      To achieve this goal, it is enough to introduce the concept of a queue of executed commands, where any command (object, successor of Command) gets after execution and a queue of canceled commands, to which any canceled command goes. In addition, it is necessary to have separate mechanisms for executing and canceling team actions. Obviously, these mechanisms are individual for each team. Then, for the implementation of a complete system, it’s enough for us, using two queues, to apply the corresponding actions to the objects located at their top - execution or cancellation.

      Class diagram


      Each command supported by the shell must extend the Command class, while implementing three methods - execute (), cancel () and name () - execution, cancellation and name of the command, respectively.

      The shell class is shown for illustrative purposes only. In the given implementation, the main () method replaces it.


      Python implementation


      # -*- coding: cp1251 -*-
      from sys import stdout as console
      # Обработка команды exit
      class SessionClosed(Exception):
      	def __init__(self, value):
      		self.value = value
      # Интерфейс команды
      class Command:
      	def execute(self):
      		raise NotImplementedError()
      	def cancel(self):
      		raise NotImplementedError()		
      	def name():
      		raise NotImplementedError()
      # Команда rm
      class RmCommand(Command):
      	def execute(self):
      		console.write("You are executed \"rm\" command\n")
      	def cancel(self):
      		console.write("You are canceled \"rm\" command\n")
      	def name(self):
      		return "rm"
      # Команда uptime
      class UptimeCommand(Command):
      	def execute(self):
      		console.write("You are executed \"uptime\" command\n")
      	def cancel(self):
      		console.write("You are canceled \"uptime\" command\n")
      	def name(self):
      		return "uptime"
      # Команда undo
      class UndoCommand(Command):
      	def execute(self):
      		try:
      			cmd = HISTORY.pop()
      			TRASH.append(cmd)
      			console.write("Undo command \"{0}\"\n".format(cmd.name()))
      			cmd.cancel()
      		except IndexError:
      			console.write("ERROR: HISTORY is empty\n")
      	def name(self):
      		return "undo"
      # Команда redo
      class RedoCommand(Command):
      	def execute(self):
      		try:
      			cmd = TRASH.pop()
      			HISTORY.append(cmd)
      			console.write("Redo command \"{0}\"\n".format(cmd.name()))
      			cmd.execute()
      		except IndexError:
      			console.write("ERROR: TRASH is empty\n")
      	def name(self):
      		return "redo"
      # Команда history
      class HistoryCommand(Command):
      	def execute(self):
      		i = 0
      		for cmd in HISTORY:
      			console.write("{0}: {1}\n".format(i, cmd.name()))
      			i = i + 1
      	def name(self):
      		print "history"
      # Команда exit
      class ExitCommand(Command):
      	def execute(self):
      		raise SessionClosed("Good bay!")
      	def name(self):
      		return "exit"
      # Словарь доступных команд
      COMMANDS = {'rm': RmCommand(), 'uptime': UptimeCommand(), 'undo': UndoCommand(), 'redo': RedoCommand(), 'history': HistoryCommand(), 'exit': ExitCommand()}   
      HISTORY = list()
      TRASH = list()
      # Шелл
      def main():
      	try:
      		while True:
      			console.flush()
      			console.write("pysh >> ")
      			cmd = raw_input()
      			try:
      				command = COMMANDS[cmd]
      				command.execute() 
      				if not isinstance(command, UndoCommand) and not isinstance(command, RedoCommand) and not isinstance(command, HistoryCommand):
      					TRASH = list()
      					HISTORY.append(command)
      			except KeyError:
      				console.write("ERROR: Command \"%s\" not found\n" % cmd)
      	except SessionClosed as e:
      		console.write(e.value)		
      if __name__ == "__main__": main()
      


      Using


      $ python pysh.py
      pysh >> rm
      You are executed "rm" command
      pysh >> rm
      You are executed "rm" command
      pysh >> rm
      You are executed "rm" command
      pysh >> history
      0: rm
      1: rm
      2: rm
      pysh >> undo
      Undo command "rm"
      You are canceled "rm" command
      pysh >> exit
      Good bay!
      


      Note


      In some sources, the template is called an Action or Transaction .

    Also popular now: