The memento pattern is a software design pattern that provides the ability to restore an object to its previous state (undo via rollback).
The memento pattern is implemented with three objects: the originator, a caretaker and a memento.
The originator is some object that has an internal state. The caretaker is going to do something to the originator, but wants to be able to undo the change. The caretaker first asks the originator for a memento object. Then it does whatever operation (or sequence of operations) it was going to do. To roll back to the state before the operations, it returns the memento object to the originator. The memento object itself is an opaque object (one which the caretaker cannot, or should not, change). When using this pattern, care should be taken if the originator may change other objects or resources - the memento pattern operates on a single object.
출처) 위키피디아
memento pattern에는 3가지 객체가 필요하다.
1) originator
originator
미국·영국 [ərídƷənèitər] 영국식 다른 뜻(1건) 예문보기
창작자, 창설자, 창시자, 발기인, 시조
가. Sets and Gets values from the currently targeted memento object.
나. Creates new Mementos and assigns current values to them.
2) caretaker
caretaker
미국식 [|kerteɪkə(r)] / 영국식 [|keəteɪkə(r)]
1. (건물의) 경비원 2. (주택・토지의) 관리인 3. 다른 사람을 돌보는 사람
Holds an ArrayList that contains all previous versions of the Memento.
It can store and retrieve stored Mementos.
3) menento
memento 미국식 [mə|mentoʊ] 영국식 [mə|mentəʊ] (사람・장소를 기억하기 위한) 기념품 : 기본 object로 다른 상태들을 저장 한다.
memento pattern은 이전 상태의 객체를 쉽게 저장하는 방법 중에 하나이다.
즉, undo~!! 이전 상태로 돌리는 것이다. 이전 상태로 돌릴려면..어떻게?????
☞ 이전 상태의 오브젝트의 정보를 저장 해야한다.
오브젝트의 정보를 복원하기 위해서는 자유자재로 해당 오브젝트를 액세스 할 수 있어야 할 것이다.
하지만 이런식으로 오브젝트를 리플렉션을 하다가는 캡슐화가 보장받지 못하게 된다.
그래서!! 캡슐화도 보장받고, 저장 및 이전상태로 돌리는(복원) undo를 가능케하는 것이 바로 memento pattern 인 것이다!(멋진데..)
Originator - Originator는 자신의 현재 상태를 저장하고 싶을 때 Memento를 만든다.(createMemento) 또한, 이전의 Memento object를 전달받으면 그 Memento object를 만든 시점의 상태로 돌리는 처리를 실행한다.
package kr.pe.acet.memento;
import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Random; public class Gamer { private int money; private List<String> fruits = new ArrayList<String>(); 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){ money += 100; System.out.println("소지금이 증가했습니다."); }else if(dice == 2){ money /= 2; System.out.println("소지금이 절반이 되었습니다."); }else if(dice == 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){ this.money = memento.money; this.fruits = memento.getFruits(); } public String toString(){ return "[money = " + money + ", fruits = " + fruits + "]"; } private String getFruit(){ String prefix = ""; if(random.nextBoolean()){ prefix = "맛있는 "; } return prefix + fruitsname[random.nextInt(fruitsname.length)]; } }
|
Memento의 역할 - Memento 역할은 Originator 역할의 내부 정보를 정리한다. Memento 역할은 Originator 역할의 내부 정보를 가지고 있지만, 그 정보를 누구에게도 공개하지 않는다. Memento 역할은 다음 두 종류의 인터페이스를 가지고 있다.
1. wide interface : Memento class에서 제공하는 이 인터페이스는 오브젝트의 상태를 원래의 상태로 돌리기 위해 필요한 정보를 모두 얻을 수 있는 메소드의 집합이다. 이 인터페이스는 Memento 역할의 내부상태를 속속들이 들어내기 때문에 이것을 사용하는 것은 Originator class 뿐이다. 2. narrow interface : Memento 역할이 제공하는 이 인터페이스는 외부의 Caretaker class에게 보여주는 것이다.
이 인터페이스로 할 수 있는 일에는 한계가 있고 내부 상태가 외부에 공개되는 것을 방지한다.
이 두 종류의 인터페이스를 구별해서 사용하면 오브젝트의 캡슐화를 보장 받을 수 있다.
package kr.pe.acet.memento;
import java.util.ArrayList; import java.util.List; public class Memento { int money; ArrayList<String> fruits; public int getMoney(){ //narrow interface(외부의 Caretaker class에게 보여주는 것) return money; } Memento(int money){ //wide interface(오브젝트의 상태를 원래의 상태로 돌리기 위해..Originator class에서 접근) this.money = money; this.fruits = new ArrayList<String>(); } void addFruit(String fruit){ //wide interface fruits.add(fruit); } @SuppressWarnings("unchecked") List<String> getFruits(){ //wide interface return (List<String>)fruits.clone(); } }
|
Caretaker의 역할 - Caretaker 역할은 현재의 Originator 역할의 상태를 저장하고 싶을 때, 그것을 Originator 역할에게 전한다. Originator 역할은 그것을 받아서 Memento 역할을 만들어 Caretaker 역할에게 전달한다. Caretaker 역할은 미래의 필요에 대비해서 그 Memento 역할을 저장해 둔다. Caretaker 역할은 Memento 역할이 갖는 2종류의 인터페이스중에서 narrow interface만 사용할 수 있으므로 Memento 역할의 내부 정보에 액세스할 수 없다.
package kr.pe.acet.memento;
import static org.junit.Assert.*;
import org.junit.Test;
public class Caretaker {
@Test public void careTakerTest() { Gamer gamer = new Gamer(100); // originator Memento memento = gamer.createMemento(); for(int i = 0; i < 10; i++){ System.out.println("==== " + i); System.out.println("현상 : " + gamer); // return "[money = " + money + ", fruits = " + fruits + "]"; gamer.bet(); System.out.println("소지금은 " + gamer.getMoney() + "원이 되었습니다."); 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){ e.printStackTrace(); } System.out.println(""); } }
}
|
결과
==== 0 현상 : [money = 100, fruits = []] 과일(맛있는 포도)을 받았습니다. 소지금은 100원이 되었습니다.
==== 1 현상 : [money = 100, fruits = [맛있는 포도]] 변한 것이 없습니다. 소지금은 100원이 되었습니다.
==== 2 현상 : [money = 100, fruits = [맛있는 포도]] 소지금이 증가했습니다. 소지금은 200원이 되었습니다. (많이 증가했으므로 현재의 상태를 저장하자)
==== 3 현상 : [money = 200, fruits = [맛있는 포도]] 변한 것이 없습니다. 소지금은 200원이 되었습니다.
==== 4 현상 : [money = 200, fruits = [맛있는 포도]] 과일(귤)을 받았습니다. 소지금은 200원이 되었습니다.
==== 5 현상 : [money = 200, fruits = [맛있는 포도, 귤]] 변한 것이 없습니다. 소지금은 200원이 되었습니다.
==== 6 현상 : [money = 200, fruits = [맛있는 포도, 귤]] 변한 것이 없습니다. 소지금은 200원이 되었습니다.
==== 7 현상 : [money = 200, fruits = [맛있는 포도, 귤]] 변한 것이 없습니다. 소지금은 200원이 되었습니다.
==== 8 현상 : [money = 200, fruits = [맛있는 포도, 귤]] 변한 것이 없습니다. 소지금은 200원이 되었습니다.
==== 9 현상 : [money = 200, fruits = [맛있는 포도, 귤]] 변한 것이 없습니다. 소지금은 200원이 되었습니다. |
Tip. 스택, 큐 등을 이용하여 히스토리로써 관리할 수도 있다. 또한 serialization을 통해서 파일로 관리하도록 기능을 확장할 수 있다.
마지막으로 요 동영상은 꼭보자!
http://www.youtube.com/watch?v=jOnxYT8Iaoo
참고 사이트 : http://scotty83.tistory.com/entry/Memento-Pattern
참고 사항
2013/11/24 - [Architecture/DesignPattern] - [DesignPattern] observer pattern
2013/11/24 - [Architecture/DesignPattern] - [DesignPattern] Interpreter pattern
2013/07/21 - [Architecture/DesignPattern] - [첫번째 스터디] singleton 패턴
2013/12/03 - [Architecture/DesignPattern] - [DesignPattern] command pattern
2013/12/09 - [Architecture/DesignPattern] - [DesignPattern] mediator pattern
2013/07/21 - [Architecture/DesignPattern] - [첫번째 스터디] abstractFactory 패턴