ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Memento
    Modeling/DesignPattern 2020. 3. 1. 19:07

    1. Overview

    • When we want to store an object's state without exposing internal details about the state then we can use memento design pattern.
    • The main intent behind saving state is often because we want to restore the object to a saved state
    • Using memento we can ask an object to give its state as a single, sealed object and store it for later use. This object should not expose the state for modification
    • This pattern is often combined with Command design pattern to provide undo functionality in an application.

    2. Description

    2.1 Implementation

    • We start by finding originator state which is to be stored in memento
    • We then implement the memento with requirement that it can't be changed and read outside the originator
    • Originator provides a method to get its current snapshot out, which will return an instance of memento
    • Another method in originator takes a memento object as argument and the originator object resets itself to match with the state in memento

    2.2 Consideration

    2.2.1 Implementation

    • It is important to keep an eye on the size of state stored in memento. A solution for discarding order state may be needed to handle large memory consumption scenarios
    • Memento often ends up being an inner class due to the requirement that it must encapsulate all details of what is stored in its instance
    • Resetting to the previous state should consider effects on states of other objects/services

    2.2.2 Design

    • If there is a definite, fixed way in which mementos are created then we can only store incremental state in mementos. This is especially true if we are using command design pattern where every command stores a memento before execution
    • Mementos can be stored internally by the originator as well but this complicates the originator. An external caretaker with fully encapsulated Memento provides you with more flexibility in implementation.

    2.3 Pitfalls

    • In practice, creating a snapshot of state may not be easy if other objects are part of originator's state
    • Resetting a state may not be as simple as copying references. If state change of originator is tied with other parts of an application then those parts may become out of sync/invalid due to resetting state.

    3. Usage

    • The undo support provided by the javax.swing.text.JTextComponent and its child classes like JTextField, JTextArea, and etc.
    • The javax.swing.undo.UndoManager acts as the caretaker and implementations of javax.swing.undo.UndoableEdit interface work as mementos. The javax.swing.text.Document implementation which is model for text components in swing is the originator.

    4. Comparison with Command

    Memento Command
    State of memento is sealed for everyone except originator Although commands are typically immutable their state is often readable.
    A memento needs to be stored for it to be of any use Commands can be stored as well but not storing them after execution is optional

    5. Example

    class Workflow {
        private LinkedList<String> steps;
        private String name;
        public Workflow(String name) {
            this.name = name;
            this.steps = new LinkedList<>();
        }
        public Workflow(String name, String... steps) {
            this.name = name;
            this.steps = new LinkedList<>();
            if(steps != null && steps.length > 0) {
                Arrays.stream(steps).forEach(s->this.steps.add(s));
            }
        }
    
        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder("Workflow [name=");
            builder.append(name).append("]\nBEGIN -> ");
            for(String step : steps) {
                builder.append(step).append(" -> ");
            }
            builder.append("END");
            return builder.toString();
        }
    
        public void addStep(String step) {
            steps.addLast(step);
        }
        public boolean removeStep(String step) {
            return steps.remove(step);
        }
        public String[] getSteps() {
            return steps.toArray(new String[steps.size()]);
        }
        public String getName() {
            return name;
        }
    }
    class WorkflowDesigner {
        private Workflow workflow;
        public void createWorkflow(String name) {
            workflow = new Workflow(name);
        }
        public Workflow getWorkflow() {
            return this.workflow;
        }
        public Memento getMemento() {
            if(workflow == null) {
                return new Memento();
            }
            return new Memento(workflow.getSteps(), workflow.getName());
        }
    //    store states
        public void setMemento(Memento memento) {
            if(memento.isEmpty()) {
                this.workflow = null;
            } else {
    //            reset exact equal to memento
                this.workflow = new Workflow(memento.getName(), memento.getSteps());
            }
        }
        public void addStep(String step) {
            workflow.addStep(step);
        }
        public void removeStep(String step) {
            workflow.removeStep(step);
        }
        public void print() {
            System.out.println(workflow);
        }
        // memento
    //     snapshot of workflow designer
        public class Memento {
            /* we have to copy states deeply*/
            private String[] steps;
            private String name;
            private Memento() {}
            // Only designer create memento
            private Memento(String[] steps, String name) {
                this.steps = steps;
                this.name = name;
            }
            // prevent changing and seeing states
            private String[] getSteps() {
                return steps;
            }
    
            private String getName() {
                return name;
            }
            private boolean isEmpty() {
                return this.getSteps() == null && this.getName() == null;
            }
        }
    }
    interface WorkflowCommand {
        void execute();
        void undo();
    }
    abstract class AbstractWorkflowCommand implements WorkflowCommand {
    
        protected WorkflowDesigner.Memento memento;
    
        protected WorkflowDesigner receiver;
    
        public AbstractWorkflowCommand(WorkflowDesigner designer) {
            this.receiver = designer;
        }
    
        @Override
        public void undo() {
            receiver.setMemento(memento);
        }
    }
    class RemoveStepCommand extends AbstractWorkflowCommand {
    
        private String step;
    
        public RemoveStepCommand(WorkflowDesigner designer, String step) {
            super(designer);
            this.step = step;
        }
    
        @Override
        public void execute() {
            memento = receiver.getMemento();
            receiver.removeStep(step);
        }
    }
    class CreateCommand extends AbstractWorkflowCommand {
    
        private String name;
    
        public CreateCommand(WorkflowDesigner designer, String name) {
            super(designer);
            this.name = name;
        }
    
        @Override
        public void execute() {
            this.memento = receiver.getMemento();
            receiver.createWorkflow(name);
        }
    
    }
    class AddStepCommand extends AbstractWorkflowCommand {
    
        private String step;
    
        public AddStepCommand(WorkflowDesigner designer, String step) {
            super(designer);
            this.step = step;
        }
    
        @Override
        public void execute() {
            this.memento = receiver.getMemento();
    
            receiver.addStep(step);
        }
    }
    public class Client {
        public static void main(String[] args) {
            WorkflowDesigner designer = new WorkflowDesigner();
            LinkedList<WorkflowCommand> commands = runCommands(designer);
            designer.print();
            commands.removeLast().undo();
            designer.print();
            commands.removeLast().undo();
            designer.print();
    
        }
    
        private static void undoLastCommand(LinkedList<WorkflowCommand> commands) {
            if(!commands.isEmpty())
                commands.removeLast().undo();
        }
    
        private static LinkedList<WorkflowCommand> runCommands(WorkflowDesigner designer) {
            LinkedList<WorkflowCommand> commands = new LinkedList<>();
    
            WorkflowCommand cmd = new CreateCommand(designer,"Leave Workflow");
            commands.addLast(cmd);
            cmd.execute();
    
            cmd = new AddStepCommand(designer,"Create Leave Application");
            commands.addLast(cmd);
            cmd.execute();
    
            cmd = new AddStepCommand(designer,"Submit Application");
            commands.addLast(cmd);
            cmd.execute();
    
            cmd = new AddStepCommand(designer,"Application Approved");
            commands.addLast(cmd);
            cmd.execute();
    
            return commands;
        }
    }

    6. Reference

    https://en.wikipedia.org/wiki/Memento_pattern

    https://courseprobe.com/instructors/coffee-powered-crew/

    'Modeling > DesignPattern' 카테고리의 다른 글

    Model View Presenter (MVP)  (0) 2020.04.10
    Mediator  (0) 2020.03.01
    Composite  (0) 2020.02.29
    Chain of responsibility  (0) 2020.02.29
    Visitor  (0) 2020.02.29

    댓글

Designed by Tistory.