Java

[Design Pattern] 추억거리 패턴(Memento Pattern)

구루싸 2020. 7. 16. 21:24
반응형
SMALL

이번 학습 주제는

추억거리(Memento) 패턴입니다

문서를 작성할 때

필요한 글을 실수로 삭제하게 되면

undo(실행취소) 기능을 사용합니다

객체 지향의 프로그램에서

이 기능을 실행하려면

인스턴스가 가진

정보를 저장해야하고

인스턴스 내부 정보에

접근 가능해야합니다

하지만 접근의 허용은

자칫 캡슐화의 파괴를

초래할 수 있습니다

Memento 패턴은

캡슐화의 파괴에 빠지지 않고도

할 수 있도록 해줍니다

역할 설명
Originator(작성자) 자신의 현재 상태를 저장하고 싶을 때 Memento 역할을 생성하고 이전의 Memento 역할을 전달 받으면 그 Memento 역할을 만든 시점의 상태로 돌리는 처리를 실행
Memento(추억거리) Originator 역할의 내부 정보를 정리하며 그 정보는 비공개함
Memento 역할은 두 종류의 인터페이스를 가짐
1. Wide Interface
 오브젝트의 상태를 원래의 상태로 돌리기 위해 필요한 정보를 모두 얻을 수 있는 메소드의 집합으로 Originator 역할만 사용 가능
2. Narrow Interface
 외부의 Caretaker 역할에게 보여주는 것으로 할 수 있는 일의 한계가 있으며 내부 상태가 외부에 공개되는 것을 방지
Caretaker(관리인) Memento 역할을 한 덩어리의 블랙박스로서 통째로 저장

이번 역할들은 다소 설명이 기네요-_-

구현을 해봐야 감을

잡을 수 있을 것 같네요

1. 클래스(Class) Memento.java

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

2. 클래스(Class) Gamer.java

package mementoPattern.game;
import java.util.*;
public class Gamer {
	private int money;
	private List<String> fruits = new ArrayList<String>();
	private Random random = new Random();
	private static String[] fruitsname = {
			"Apple", "Banana", "Tangerine", "Grape",
	};
	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 fruit = getFruit();
			System.out.println("과일(" + fruit + ")을 받았습니다");
			fruits.add(fruit);
		} else {
			System.out.println("변한 것이 없습니다");
		}
	}
	public Memento createMemento() {
		Memento m = new Memento(money);
		Iterator<String> it = fruits.iterator();
		while ( it.hasNext() ) {
			String fruit = (String) it.next();
			if ( fruit.startsWith("맛있는") ) {
				m.addFruit(fruit);
			}
		}
		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)];
	}
}

3. 클래스(Class) Test.java

import mementoPattern.game.*;
public class Test {
	public static void main(String[] args) {
		/* Memento Pattern */
		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 ) {
				/* Ignore */
			}
			System.out.println("");
		}
	}
}

위의 코드는

메모리 상에서만

Memento를 저장합니다

이것을 파일로

저장하도록 변경하겠습니다

4. 클래스(Class) Memento.java

package mementoPattern.game;
import java.io.*;
import java.util.*;
public class Memento implements Serializable {
	int money;
	ArrayList<String> fruits;
	public int getMoney() {
		return money;
	}
	Memento(int money) {
		this.money = money;
		this.fruits = new ArrayList<String>();
	}
	void addFruit(String fruit) {
		fruits.add(fruit);
	}
	List<String> getFruits() {
		return (List<String>)fruits.clone();
	}
}

5. 클래스(Class) Test.java

import mementoPattern.game.*;
import java.io.*;
import java.util.zip.*;
public class Test {
	public static final String SAVEFILENAME = "game.dat";
	public static void main(String[] args) {
		/* Memento Pattern */
		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.bet();
			
			System.out.println("소지금은" + gamer.getMoney() + "원이 되었습니다");
			
			if ( gamer.getMoney() > memento.getMoney() ) {
				System.out.println(" (많이 증가했으므로 현재의 상태를 저장하자) ");
				memento = gamer.createMemento();
				saveMemento(memento);
			} else if ( gamer.getMoney() < memento.getMoney() / 2 ) {
				System.out.println(" (많이 감소했으므로 이전의 상태로 복원하자) ");
				gamer.restoreMemento(memento);
			}
			
			try {
				Thread.sleep(1000);
			} catch ( InterruptedException e ) {
				/* Ignore */
			}
			System.out.println("");
		}
	}
	public static void saveMemento(Memento memento) {
		try {
			ObjectOutput out = new ObjectOutputStream(new DeflaterOutputStream(new FileOutputStream(SAVEFILENAME)));
			out.writeObject(memento);
			out.close();
		} catch ( IOException e ) {
			e.printStackTrace();
		}
	}
	public static Memento loadMemento() {
		Memento memento = null;
		try {
			ObjectInput in = new ObjectInputStream(new InflaterInputStream(new FileInputStream(SAVEFILENAME)));
			memento = (Memento)in.readObject();
			in.close();
		} catch ( FileNotFoundException e ) {
			System.out.println(e.toString());
		} catch ( IOException e ) {
			e.printStackTrace();
		} catch ( ClassNotFoundException e ) {
			e.printStackTrace();
		}
		return memento;
	}
}

Gamer 클래스의 변화없이

새로운 기능이 추가되었습니다

이렇게 될 수 있는 이유는

역할 분담이 되어 있기 때문입니다

이번 학습에서는

Serializable과 자바의 파일 압축도

함께 학습할 수 있었네요~

아무튼 이것으로

오늘의 학습을 마치겠습니다

그럼 이만-_-

반응형
LIST