Command Pattern, Java and Python Example

Command Pattern, Java and Python Example

From Wikipedia In object-oriented programming, the command pattern is a behavioral design pattern in which an object is used to represent and encapsulate all the information needed to call a method at a later time. This information includes the method name, the object that owns the method and values for the method parameters. This post shows an example implemented on Java and Python.

Multilevel Undo Example

Lest suppouse that we have an editor, an user can copy , cut and paste text inside a screen. If all user actions in this program are implemented as command objects, the program can keep a stack of the most recently executed commands. When the user wants to undo a command, the program simply pops the most recent command object and executes its undo() method.

Java Implementation

The Receiver

Is the object that will be manipulated by a command, in this particular example the Receiver Object is a Screen Object.

    package co.ntweb;

    public class Screen {
        private StringBuffer text;
        private StringBuffer clipBoard;

        public Screen(String text){
            this.text = new StringBuffer(text);
            this.clipBoard = new StringBuffer();
        }

        public void clearClipBoard(){
            this.clipBoard.delete(0, this.clipBoard.length());
        }

        public void cut(int start, int end){
            clearClipBoard();
            this.clipBoard.append(this.text.substring(start, end));
            this.text.delete(start, end);
        }

        public void copy(int start, int end){
            clearClipBoard();
            this.clipBoard.append(this.text.substring(start, end));
        }

        public void paste(int offset){
            this.text.insert(offset, getClipBoard());
        }

        public String getClipBoard(){
            return this.clipBoard.toString();
        }

        public String toString(){
            return this.text.toString();
        }

        public void setText(String text){
            this.text = new StringBuffer(text);
        }

        public int length(){
            return this.text.length();
        }
    }
Command Objects

Command Object implents all the logic that the command needs, this include undo instructions and screen state before execute command.


    package co.ntweb;

    public interface Command {
        void execute();
        void undo();
    }


    package co.ntweb;

    import co.ntweb.Command;
    import co.ntweb.Screen;

    public class CopyCommand implements Command{
        private Screen screen;
        private String previous_status;
        private int start;
        private int end;

        public CopyCommand(Screen screen, int start, int end){
            this.screen = screen;
            this.previous_status = screen.toString();
            this.start = start;
            this.end = end;
        }

        public void execute(){
            this.screen.copy(this.start, this.end);
        }

        public void undo(){
            this.screen.clearClipBoard();
        }
    }



    package co.ntweb;

    import co.ntweb.Command;
    import co.ntweb.Screen;

    public class CutCommand implements Command{
        private Screen screen;
        private String previousStatus;
        private int start;
        private int end;

        public CutCommand(Screen screen, int start, int end){
            this.screen = screen;
            this.previousStatus = screen.toString();
            this.start = start;
            this.end = end;
        }

        public void execute(){
            this.screen.cut(this.start, this.end);
        }

        public void undo(){
            this.screen.clearClipBoard();
            this.screen.setText(this.previousStatus);
        }
    }



    package co.ntweb;

    import co.ntweb.Command;
    import co.ntweb.Screen;

    public class PasteCommand implements Command{
        private Screen screen;
        private String previousStatus;
        private int offset;

        public PasteCommand(Screen screen, int offset){
            this.screen = screen;
            this.previousStatus = screen.toString();
            this.offset = offset;
        }

        public void execute(){
            this.screen.paste(this.offset);
        }

        public void undo(){
            this.screen.clearClipBoard();
            this.screen.setText(this.previousStatus);
        }
    }


Invoker Object

The button, toolbar button, or menu item clicked, the shortcut key pressed by the user.

    package co.ntweb;

    import co.ntweb.Command;

    import java.util.LinkedList;

    public class ScreenInvoker{
        private LinkedList history = new LinkedList();

        public ScreenInvoker(){
        }

        public void storeAndExecute(Command cmd) {
          this.history.add(cmd);
          cmd.execute();
        }

        public void undoLast(){
            this.history.removeLast().undo();
        }

    }
Pattern usage
        Screen editor = new Screen("hello world!!");
        ScreenInvoker client = new ScreenInvoker();

        CutCommand cutCommand = new CutCommand(editor, 5, 11);
        client.storeAndExecute(cutCommand);
        assertEquals("hello!!", editor.toString());

        PasteCommand pasteCommand = new PasteCommand(editor, 0);
        client.storeAndExecute(pasteCommand);
        assertEquals(editor.toString(), " worldhello!!");

        CopyCommand copyCommand = new CopyCommand(editor, 0, editor.length());
        client.storeAndExecute(copyCommand);

        PasteCommand pasteCommand2 = new PasteCommand(editor, 0);
        client.storeAndExecute(pasteCommand2);
        assertEquals(editor.toString(), " worldhello!! worldhello!!");

        //undo last paste
        client.undoLast();
        assertEquals(editor.toString(), " worldhello!!");

        //undo copy
        client.undoLast();
        assertEquals(editor.toString(), " worldhello!!");

        //undo paste
        client.undoLast();
        assertEquals("hello!!", editor.toString());

        //undo cut
        client.undoLast();
        assertEquals("hello world!!", editor.toString());

Python Implementation

The Receiver
    class Screen(object):
        def __init__(self, text=''):
            self._text = text
            self._clip_board = ''

        @property
        def text(self):
            return self._text

        @property
        def clipboard(self):
            return self._clip_board

        def copy(self, start=0, end=0):
            self._clip_board = self._text[start:end]

        def cut(self, start=0, end=0):
            self._clip_board = self._text[start:end]
            self._text = self._text[:start] + self._text[end:]

        def paste(self, offset=0):
            self._text = self._text[:offset] + self._clip_board + self._text[offset:]

        def clear_clipboard(self):
            self._clip_board = ''

        def length(self):
            return len(self._text)

        def __str__(self):
            return self._text
The Commands
    class Command(object):
        _previous_status = ''

        def execute(self):
            pass

        def undo(self):
            pass


    class CopyCommand(Command):
        def __init__(self, screen, start=0, end=0):
            self._screen = screen
            self._start = start
            self._end = end
            self._previous_status = screen.text

        def execute(self):
            self._screen.copy(start=self._start, end=self._end)

        def undo(self):
            self._screen.clear_clipboard()

    class CutCommand(Command):
        def __init__(self, screen, start=0, end=0):
            self._screen = screen
            self._start = start
            self._end = end
            self._previous_status = screen.text

        def execute(self):
            self._screen.cut(start=self._start, end=self._end)

        def undo(self):
            self._screen.clear_clipboard()
            self._screen._text = self._previous_status

    class PasteCommand(Command):
        def __init__(self, screen, offset=0):
            self._screen = screen
            self._offset = offset
            self._previous_status = screen.text

        def execute(self):
            self._screen.paste(offset=self._offset)

        def undo(self):
            self._screen.clear_clipboard()
            self._screen._text = self._previous_status
The Invoker
    class ScreenInvoker(object):
        def __init__(self):
            self._history = []

        def store_and_execute(self, command):
            command.execute()
            self._history.append(command)

        def undo_last(self):
            if self._history:
                self._history.pop().undo()
Pattern Usage
    screen = Screen('hello world!!')
    client = ScreenInvoker()

    cut_command = CutCommand(screen, start=5, end=11)
    client.store_and_execute(cut_command)

    paste_command = PasteCommand(screen, offset=0)
    client.store_and_execute(paste_command)

    copy_command = CopyCommand(screen, start=0, end=screen.length())
    client.store_and_execute(copy_command)

    paste_command2 = PasteCommand(screen, offset=0)
    client.store_and_execute(paste_command2)

    #undo last paste
    client.undo_last()

    #undo copy
    client.undo_last()
    self.assertEquals(' worldhello!!', screen.text)

    #undo paste
    client.undo_last()
    self.assertEquals('hello!!', screen.text)

    #undo cut
    client.undo_last()
    self.assertEquals('hello world!!', screen.text)

Source code can be checked here