2015년 11월 2일 월요일

클래스 디자인 패턴 21. Memento Pattern

1. 개념
  1) 의미
     문서 편집이나 그림 작업시 이전 상태로 되돌리기 위해서는
       이전 상태에 대한 정보가 남아 있어야 됩니다.
     Memento 패턴은 이전 상태를 담아두되
       인스턴트 상태를 나타내는 역할을 부여해 보존, 복원이
       실행되도록 하는 패턴입니다.


2. 소스

  1) Memento class
       현재 상태를 표현하는 클래스입니다.
package game;
import java.util.Vector;

public class Memento {
    int money;                  
         // 돈
    Vector fruits;            
        // 과일
    public int getMoney() {
       // 돈을 얻는다.(narrow interface)
        return money;
    }
    Memento(int money) {
       // 생성자(wide interface)
        this.money = money;
        this.fruits = new Vector();
    }
    void addFruit(String fruit) {
       // 과일을 추가한다.(wide interface)
        fruits.add(fruit);
    }
}


  2) Gamer 클래스
      현재 뭔가 작업이 이루어지는 클래스
      여기에서는 난수 및 시간을 딜레이 하는 기능을 이용해
      일정 시간별로 한 사이클씩 작업이 이루어집니다.

package game;
import java.util.Vector;
import java.util.Iterator;
import java.util.Random;

public class Gamer {
    private int money;                          // 소유한 돈
    private Vector fruits = new Vector();       // 과일
    private Random random = new Random();       // 난수발생기
    private static String[] fruitsname = {      // 과일 이름의 표
        "사과", "포도", "바나나", "귤",
    };
    public Gamer(int money) {                   // 생성자
        this.money = money;
    }
    public int getMoney() {                     // 현재의 돈을 얻는닫.
        return money;
    }
    public void bet() {                         // 졌다...게임의 진행
        int dice = random.nextInt(6) + 1;           // 주사위를 던진다.
        if (dice == 1) {                            // 1이 나온다...돈이 증가한다.
            money += 100;
            System.out.println("돈이 증가했습니다.");
        } else if (dice == 2) {                     // 2가 나온다...돈이 반으로 준다.
            money /= 2;
            System.out.println("돈이 반으로 줄었습니다.");
        } else if (dice == 6) {                     // 6이 나온다...과일을 받는다.
            String f = getFruit();
            System.out.println("과일(" + f + ")을 받았습닏다.");
            fruits.add(f);
        } else {                                    // 그 밖에...아무 일도 일어나지 않는다.
            System.out.println("아무일도 일어나지 않았습니다.");
        }
    }
    public Memento createMemento() {                // 스냅샷을 찍는다.
        Memento m = new Memento(money);
        Iterator it = fruits.iterator();
        while (it.hasNext()) {
            String f = (String)it.next();
            if (f.startsWith("맛있다.")) {         // 과일은 맛있는 것만 보존
                m.addFruit(f);
            }
        }
        return m;
    }
    public void restoreMemento(Memento memento) {       // undo를 실행한다.
        this.money = memento.money;
        this.fruits = memento.fruits;
    }
    public String toString() {                      // 문자열 표현
        return "[money = " + money + ", fruits = " + fruits + "]";
    }
    private String getFruit() {                     // 과일을 1개 얻는다.
        String prefix = "";
        if (random.nextBoolean()) {
            prefix = "맛있다.";
        }
        return prefix + fruitsname[random.nextInt(fruitsname.length)];
    }
}



  3) 테스트 메인소스를 작성합니다.
      여기서는 작업이 진행된 상태를 사이클이 지날때마다
      보여주는 작업도 같이 합니다.
import game.Memento;
import game.Gamer;

public class Main {
    public static void main(String[] args) {
        Gamer gamer = new Gamer(100);               // 처음의 돈은 100
        Memento memento = gamer.createMemento();    // 처음의 상태를 보존해 둔다.
        for (int i = 0; i < 100; i++) {
            System.out.println("==== " + i);        // 횟수 표시
            System.out.println("현 상태:" + gamer);    // 현재의 주인공의 상태 표시

            gamer.bet();    // 게임을 진행 시킨다.

            System.out.println("돈은" + gamer.getMoney() + "원이 되었습니다.");

            // Memento의 취급 결정
            if (gamer.getMoney() > memento.getMoney()) {
                System.out.println("    (많이 증가했으니 현재의 상태를 보존해두자)");
                memento = gamer.createMemento();
            } else if (gamer.getMoney() < memento.getMoney() / 2) {
                System.out.println("    (많이 줄었으니 이전의 상태로 복귀하자)");
                gamer.restoreMemento(memento);
            }

            // 시간을 기다림
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            System.out.println("");
        }
    }
}


결과 :
==== 0
현 상태:[money = 100, fruits = []]
아무일도 일어나지 않았습니다.
돈은100원이 되었습니다.

==== 1
현 상태:[money = 100, fruits = []]
아무일도 일어나지 않았습니다.
돈은100원이 되었습니다.

==== 2
현 상태:[money = 100, fruits = []]
아무일도 일어나지 않았습니다.
돈은100원이 되었습니다.

==== 3
현 상태:[money = 100, fruits = []]
아무일도 일어나지 않았습니다.
돈은100원이 되었습니다.
1
==== 4
현 상태:[money = 100, fruits = []]
돈이 증가했습니다.
돈은200원이 되었습니다.
    (많이 증가했으니 현재의 상태를 보존해두자)

==== 5
현 상태:[money = 200, fruits = []]
과일(맛있다.포도)을 받았습닏다.
돈은200원이 되었습니다.

==== 6
현 상태:[money = 200, fruits = [맛있다.포도]]
과일(귤)을 받았습닏다.
돈은200원이 되었습니다.

........


3. 다이어그램

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

http://scotty83.tistory.com/entry/Memento-Pattern

4. 관련패턴
  1) Command 패턴
      undo, redo를 Memento 패턴을 사용해서 구현가능합니다.

  2) Prototype 패턴
    스냅샷 및 undo 기능을 추가하기 위해 Memento 패턴을
      넣을 수 있습니다.

  3) State 패턴
    상태를 포현한다는 점에선 같으나 State는 클래스 자체가 상태를
      표현하며 Memento는 인스턴스가 상태를 표현합니다.

댓글 없음:

댓글 쓰기