Memento 패턴

2020-06-06

Memento 패턴

Memento 패턴은 Originator 객체와 Caretaker 객체 두개가 필요합니다.

Originator 객체는 상태를 저장하고 복원해야하는 객체이며 inner 클래스를 이용하여 객체의 상태를 저장합니다. inner 클래스는 Memento라고 불리는 클래스가 있으며, 다른 객체에서 접근하지 못하도록 private으로 설정 됩니다.

Caretaker 객체는 Memento 객체를 통하여 Originator 객체의 상태를 저장하고 복원하는 헬퍼 클래스 입니다. Memento 클래스 private이기 때문에 Caretake가 접근하지 못하기 때문에 Caretake 객체 안에 구성되어있습니다.

Memento 패턴 구현해보기

실 생활 사례 중에 좋은 예제는 텍스트 편집기 입니다. 텍스트 편집기는 글을 저장하거나, 아니면 마지막으로 저장했던 글을 되돌릴 수도 있습니다.

이번 예제에서는 텍스트 편집기에서 글을 작성하면 그 글을 저장하는 기능과, 마지막으로 저장했던 글을 불러오는 기능을 구현해보겠습니다. 간단히 구현 하기 위해서 IO 동작은 구현하지 않았습니다.

FileWriterUtil.java (Originator 클래스)

//Originator 클래스
public class FileWriterUtil {

    private String fileName;
    private StringBuilder content;

    public FileWriterUtil(String fileName) {
        this.fileName = fileName;
        this.content = new StringBuilder();
    }

    @Override
    public String toString() {
        return this.content.toString();
    }

    public void write(String str) {
        this.content.append(str);
    }

    public Memento save() {
        return new Memento(this.fileName,this.content);
    }

    public void undoToLastSave(Object obj) {
        Memento memento = (Memento) obj;
        this.fileName = memento.fileName;
        this.content = memento.content;
    }
		//Memento 클래스
    private class Memento {
        private String fileName;
        private StringBuilder content;

        public Memento(String fileName, StringBuilder content) {
            this.fileName = fileName;
            //deep copy를 사용해서 FileWriterUtil의 content 변수와 동일한 레퍼런스를 갖지 않도록 함.
            this.content = new StringBuilder(content);
        }
    }
}

FileWriterCaretake.java (Caretaker 클래스)

public class FileWriterCaretaker {
    //Memento 클래스를 구성하고 있음.
    private Object object;

    public void save(FileWriterUtil fileWriterUtil) {
        this.object = fileWriterUtil.save();
    }

    public void undo(FileWriterUtil fileWriterUtil) {
        fileWriterUtil.undoToLastSave(object);
    }
}

Caretaker 클래스는 이전 객체의 상태를 갖는 Object 클래스를 구성하고 있습니다.

테스트

MementoTest.java

public class MementoTest {
    public static void main(String[] args) {

        FileWriterCaretaker caretaker = new FileWriterCaretaker();

        FileWriterUtil fileWriter = new FileWriterUtil("data.txt");
        fileWriter.write("첫번 째 데이터!! ㅎ");
        System.out.println(fileWriter);

        System.out.println("-------------------");
        // 파일 저장
        caretaker.save(fileWriter);

        //다른 데이터 작성
        fileWriter.write("두번 째 데이터 !! ㅎㅎㅎ");


        //현재 파일의 데이터 출력
        System.out.println(fileWriter);

        //작성했던 데이터 되돌리기
        caretaker.undo(fileWriter);

        System.out.println("-----------undo()--------");
        //현재 파일의 데이터 출력
        System.out.println(fileWriter);

    }
}

Output

첫번  데이터!! 
-------------------
첫번  데이터!! ㅎ두번  데이터 !! ㅎㅎㅎ
-----------undo()--------
첫번  데이터!! 

정리

Memento 패턴은 간단하고 구현하기 쉽지만, 조심해야할 점은 Memento 클래스는 오직 Originator 객체만 접근할수 있게 만들어야 한다는 점 입니다. 또한 클라이언트는 caretaker 객체만 이용해 originator 객체를 조작해야한다는 점 입니다.

Originator 객체에 있는 필드들은 immutable(변경 불가능) 하지 않지만, 예제에서 사용했던깊은 복사(deep copy)clone 메소드를 사용해서 데이터 무결성 이슈를 해결할 수 있습니다.

Originator 객체의 단점은 객체가 커지면 Memento 객체도 같이 커지기 때문에 많은 메모리를 잡아먹을 수 있습니다.