[디자인 패턴] 메멘토 패턴

한낱·2023년 10월 8일

디자인 패턴

목록 보기
5/6

메멘토 패턴

  • 의미
    : Memento라는 단어 자체는 '기념품, 유품, 추억거리' 등을 의미하며, 메멘토 패턴은 어느 시점에서 인스턴스 상태를 확실하게 기록하여 저장하고 나중에 해당 시점으로 돌리기 위해 사용된다.

  • 필요한 이유
    : 인스턴스를 복원하기 위해서는 인스턴스 내부 정보에 자유롭게 접근할 수 있어야 한다. -> 캠슐화의 파괴로 이어질 수 있다.

예제 프로그램 : 과일 모으기 주사위 게임

Memento 클래스

  • 메멘토 클래스 : Gamer의 상태를 나타내기 위해 사용되는 클래스
package game;

import java.util.ArrayList;
import java.util.List;

public class Memento {
	private int money;
    private List<String> fruits;
    
    public int getMoney() {
    	return money;
    }
    
    Memento(int money) {
    	this.money = money;
        this.fruits = new ArrayList<>();
    }
    
    void addFruit(String fruit) {
    	fruits.add(fruit);
    }
    
    List<String> getFruits() {
    	return new ArrayList<>(fruits);
    }
}
  • 멤버 변수 : money, fruits
  • 특징 : 생성자(Memento)와 addFruit 메서드의 접근 제한자에 public이 없기 때문에 동일 패키지에 속한 클래스에서만 사용할 수 있다.

Gamer 클래스

  • Gamer 클래스 : 게임 주인공을 나타내는 클래스
package game;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class Gamer {
	private int money;
    private List<String> fruits = new ArrayList<>();
    private 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);
        for (String f: fruits) {
        	if (f.startsWith("맛있는 ")) {
            	m.addFruit(f);
            }
        }
        return m;
    }
    
    public void restoreMemento(Memento memento) {
    	this.money = memento.getMoney();
        this.fruits = memento.getFruits();
    }
    
    @Override
    public String toString() {
    	return "[money = " + money + ", fruits = " + fruits + "]";
    }
    
    private String getFruit() {
    	String f = fruitsName[random.nextInt(fruitsName.length)];
        if (random.nextBoolean()) {
        	return "맛있는 " + f;
        } else {
        	return f;
        }
    }
}
  • 멤버 변수 : money, fruits, random, fruitsName
  • bet : 게임을 진행하는 메서드로 주사위의 결과에 따라 소지금의 변경 또는 과일의 변경이 생길 수 있다.
  • create/restore Memento : 스냅샷을 찍거나 / 복원하는 메서드

Main 클래스

  • Main 클래스 : Gamer 인스턴스를 만들어 게임을 진행한다.
import game.Memento;
import game.Gamer;

public class Main {
	public static void main(String[] args) {
    	Gamer gamer = new Gamer(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() + "원이 되었습니다.");
            
            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();
        }
    }
}
  • 특징 : 메멘토 패턴을 도입하여 memento에 어떤 시점의 Gamer 상태를 저장한다.
    소지금이 늘어나면 createMemento를 사용해 memento에 현재 상태를 저장하고,
    소지금이 부족해지면 restoreMemento를 사용하여 소지금을 원래대로 되돌린다.

결과


시퀀스 다이어그램

패턴 다이어그램

  1. Originator (Gamer 클래스)
    자신의 현재 상태를 저장하고 싶을 때 Memento를 만들고 이전 Memento를 넘겨받으면 그 Memento를 만든 시점으로 상태를 되돌리는 처리를 한다.

  2. Memento
    Originator의 내부 정보를 가지고 있으면서도 누구에게나 공개하지는 않는다.

  • wide interface : 오브젝트의 상태를 되돌리는 데 필요한 정보를 모두 얻을 수 있는 메소드의 집합. Memento의 내부 상태를 드러내기 때문에 Originator만 사용할 수 있다.
  • narrow interface : 외부 Caretaker에 보여지는 인터페이스
    Memento 클래스는 이를 통해 캡슐화 파괴를 막을 수 있다. (내부 상태를 공개할 대상이 정해지기 때문)
  1. Caretaker (Main 클래스)
    Originator에 요청하여 당시의 Originator 상태를 Memento 형태로 저장한다. 이 때, Caretaker는 Memento의 내부 정보에 접속할 수 없기 때문에 블랙 박스 형태로 통째로 보관하는 역할만 담당한다.

Originator 클래스는 Memento를 만드는 일, 주어진 Memento로 자신의 상태를 되돌리는 일을, Caretaker는 어느 시점에 스냅샷을 찍을지나 언제 실행 취소를 할지를 결정하는 일을 담당한다.

  • Memento 클래스 예제에서 wide / narrow interface

    package game;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class Memento {
        private int money;
        private List<String> fruits;
        
        // narrow interface
        public int getMoney() {
            return money;
        }
        
        // wide interface
        Memento(int money) {
            this.money = money;
            this.fruits = new ArrayList<>();
        }
        
        // wide interface
        void addFruit(String fruit) {
            fruits.add(fruit);
        }
        
        // wide interface
        List<String> getFruits() {
            return new ArrayList<>(fruits);
        }
    }

    public 키워드가 붙은 메서드가 narrow interface이고 main에서 접근 가능
    public이 붙지 않은 메서드는 같은 패키지인 Gamer에서만 접근 가능하며 wide interface

    // main 함수에서 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);
    }

    public 붙은 메서드에 '좁다'는 표현을 사용하는 이유는 '내부 상태를 조작할 수 있는 정도가 적다'는 의미

profile
제일 재밌는 개발 블로그(희망 사항)

0개의 댓글