CS/디자인패턴

[디자인패턴] Memento 패턴 (메멘토 패턴, Ctrl + Z)

mabb 2023. 6. 24. 16:43
반응형

▶디자인 패턴 ( Design Pattern)

:프로그램 개발 시 문제 해결을 위하여 빈번히 사용되는 개발자들의 경험, 내적인 축적에 대하여,
GoF(Gang of Four) 라 불리는 4인의 개발자들이 각각을 패턴으로 정의하고 이름을 붙였다.
이를 디자인 패턴 (Design Pattern) 이라고 한다.
 23개의 디자인 패턴을
『Elements of Reusable Object-Oriented Software』
라는 책으로 발간하였다.

Gang of Four 분들은 모두 안경을 썼다.

 

▷디자인 패턴의 용어를 빌리면 서로의 아이디어를 보다 용이하게 비교, 논의할 수 있게 된다.
▷재사용과 기능확장이 쉬운 소프트웨어를 만들기 위한 유익한 기법이 바로 디자인 패턴이다.

 

---------------------------------------------------------
1. Iterator
2. Adapter
3. Template Method
4. Factory Method
5. Singleton
6. Prototype
7. Builder
8. Abstract Factory
9. Bridge
10. Strategy
11. Composite
12. Decorator
13. Visitor
14. Chain of Responsibility
15. Facade
16. Mediator
17. Observer
18. Memento
19. State
20. Flyweight
21. Proxy
22. Command
23. Interpreter
---------------------------------------------------------

▶Memento 패턴

"Ctrl+Z ( Undo )"
"특정 시점 객체 정보를 저장할 수 있는 Memento객체"
"저장해 둔 Memento 객체의 정보를 불러와 변경하는 것이 Undo"


-왜 사용하는가
-클래스다이어그램
-각 클래스(인터페이스)의 역할
-Java소스

▶Memento 패턴에 대한 이해

-Memento는 '기억하기 위한 기념품'을 뜻한다.

-특정 시점의 객체 상태를 스냅샷 사진처럼 Memento로 저장하고, 필요시  그 상태로 돌아간다.

-Ctrl + Z 처럼 undo 기능을 수행하기 위해 사용한다.

-Memento 생성이 필요한 원본을 Original이라고 칭한다.

-Original은 현재 본인의 상태(데이터)와 같은 Memento를 생성하고, memento를 이용하여 본인의 상태를 변경하는 기능을 수행할 수 있다.

-Memento는 Original의 상태(데이터)를 저장할 수 있는 객체이다.

-언제 Oritinal의 메멘토를 저장할지, 언제 Original을 저장된 메멘토로 undo 할지 정하는 역할을 Caretaker(관리인)이라고 칭한다.

-Original의 데이터를 저장하기 위해 Original과 Memento는 긴밀한 관계를 가진다. 같은 패키지 내에서 default 접근제한자로  필요한 정보를 공유한다.

-제한된 기능을 public하게 공개하는 것을 narrow interface, 제한되지 않은 기능을 긴밀한 관계에게만 공개하는 것을 wide interface라고 칭한다.  내부 상태를 조작할 수 있는 정도가 적기 때문에 narrow 하다고 표현한다.

-Memento는 Original에는 wide interface로, Caretaker에는 narrow interface로 설정한다. 

-Memento패턴에서는 인스턴스의 데이터로  '상태'를 표현하고 State패턴에서는 클래스 자체로 상태를 표현한다.

 

▶왜 사용하는가

인스턴스의 특정 시점 데이터를 스냅샷 처럼 Memento 객체에 저장하여 필요시 해당 상태로 돌아가기 위해 사용한다. Memento 객체를 Serialize 하여 파일로 저장할 수 있다.

 

▶클래스다이어그램

Memento 패턴의 클래스다이어그램

 

▶각 클래스(인터페이스)의 역할

▷오리지날(Original) 역할 - Gamer.java 

특정 시점의 정보를 메멘토로 저장하기 위한 대상이 되는 객체이다. 특정 시점 자기 자신의 데이터를 가지는 메멘토를 생성할 수 있으며 메멘토의 데이터로 되돌아가는 기능을 수해할 수 있다. 

▷메멘토(Memento) 역할 - Memento.java

오리지널에 의해 생성된다. 오리지널이 메멘토로 백업할 특정 상태(데이터)를 저장해 두는 역할을 한다. 오리지널과 메멘토는 같은 패키지 내에서 디폴트 접근제한자로 wide interface 관계로 구성한다. 메멘토는 관리자와는 narrow interface 관계를 갖는다.

▷관리자(Caretaker) 역할 - Main.java

어떤 시점에 오리지널이 메멘토를 만들어 둘지 (스냅숏을 찍어둘지) 어떤 시점에 오리지널이 메멘토의 상태로 되돌아갈지 관리해 주는 역할을 한다.

 

▶Java 소스

*java1.8 이전 버전을 사용하는 교재를 참고 하였습니다.
Java API 문서 등을 참조하여 제네릭을 적용하는 등 소스를 개선할 필요가 있습니다.
(https://docs.oracle.com/en/java/javase/20/docs/api/)

 

▷오리지널(Original) 역할 - Gamer.java 

package main.java.designpattern.memento.game;

import java.util.*;
import java.io.*;

public class Gamer {
    private int money;
    private List<String> fruits = new ArrayList<>();
    private Random random = new Random();
    private static String[] fruitsname = {"사과", "배", "바나나", "체리"};

    public Gamer(int money){
        this.money = money;
    }
    public int getMoney(){
        return money;
    }
    public void bat(){
        int dice = random.nextInt(6) + 1;
        switch(dice){
            case 1: money+= 100; System.out.println("소지금이 증가했습니다.");break;
            case 2: money/= 2; System.out.println("소지금이 반으로 줄었습니다.");break;
            case 5: String f = getFruit(); System.out.println("과일" + f + "을 받았습니다.");fruits.add(f);break;
            default: System.out.println("변한 것이 없음");
        }
    }
    public Memento createMemento(){
        Memento m = new Memento(money);
        Iterator it = this.fruits.iterator();
        while(it.hasNext()){
            String f = (String)it.next();
            if(f.startsWith("맛있는")){
                m.addFruit(f);
            }
        }
        System.out.println("생성된 메멘토의 상태 : " +  m.toString() );
        return m;
    }
    public void restoreMemento(Memento memento){
        this.money = memento.money;
        this.fruits = memento.getFruits();
    }
    public String toString(){
        return "[money = " + money + ", fruits = " + fruits + " ]";
    }
    public String getFruit(){
        String prefix = "";
        if(random.nextBoolean()){
            prefix = "맛있는";
            System.out.println("맛있는과일체크");
        }
        return prefix + fruitsname[random.nextInt(fruitsname.length)];
    }
}

 

▷메멘토(Memento) 역할 - Memento.java

package main.java.designpattern.memento.game;

import java.io.Serializable;
import java.util.*;

public class Memento implements Serializable {
    int money;
    ArrayList<String> fruits;
    Memento(int money){
        this.money = money;
        fruits = new ArrayList<>();
    }
    public int getMoney(){
        return money;
    }
    void addFruit(String fruit){
        fruits.add(fruit);
    }
    List getFruits(){
        return (List)fruits.clone();
    }

    @Override
    public String toString(){
        return "메멘토 상태 \n 돈: "+money+", 과일: "+fruits +"";
    }
}

 

▷관리자(Caretaker) 역할 - Main.java

package main.java.designpattern.memento;

import main.java.designpattern.memento.game.Gamer;
import main.java.designpattern.memento.game.Memento;
import java.io.*;

public class Main {
    public static final String SAVEFILENAME = "game.dat";
    public static void main(String[] args){
        Gamer gamer = new Gamer(100);
        Memento memento = loadMemento();
        if(memento != null){
            System.out.println("지난 결과에서 시작합니다.");
            gamer.restoreMemento(memento);
        }else{
            System.out.println("새로 시작합니다.");
            memento = gamer.createMemento();
        }
        for(int i=0; i<100; i++){
            System.out.println("=====" + i);
            System.out.println("현재상태: " + gamer );

            gamer.bat();

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

            //Memento 취급 결정
            if(gamer.getMoney() > memento.getMoney()){
                System.out.println("많이 증가했으므로 현재의 상태를 저장한다.");
                memento = gamer.createMemento();
                saveMemento(memento);
            }else if(gamer.getMoney() < memento.getMoney()){
                System.out.println("많이 감소했으므로 이전의 상태로 복원한다.");
                gamer.restoreMemento(memento);
            }

            //대기
            try{
                Thread.sleep(3000);
            }catch(InterruptedException e){
            }
            System.out.println();
        }
    }
    public static void saveMemento(Memento memento){
        try{
            ObjectOutput out = new ObjectOutputStream(new FileOutputStream(SAVEFILENAME));
            out.writeObject(memento);
            out.close();
        }catch(Exception e){
        }
    }
    public static Memento loadMemento(){
        Memento memento = null;
        try{
            ObjectInput in = new ObjectInputStream(new FileInputStream(SAVEFILENAME));
            memento = (Memento) in.readObject();
            in.close();
        }catch(Exception e){
        }
        return memento;
    }
}

 

---------------------------------------------------------
※참고자료
-Java언어로 배우는 디자인 패턴 입문
-Headfirst Design Pattern
-나무위키, 위키백과, 네이버백과
-리팩토링구루(https://refactoring.guru/ko)

반응형