Memento

GamSa Ham·2023년 1월 17일
0

GoF디자인패턴

목록 보기
22/22

의도

  • 캡슐화를 위배하지 않은 채 어떤 객채의 내부 상태를 잡아내고 실체화시켜 둠으로써(=스냅샷) 해당 객체가 그 상태로 되돌아 올 수 있게 함

다른 이름

  • 토큰(Token)

동기

  • 체크포인트 구현이나 오류를 복구하거나 연산 수행 결과를 취소하는 메커니즘 구현 시 내부 상태 기록이 필요함
  • 객체는 자체적으로 상태의 일부나 전부를 캡슐화하여 상태를 외부에 공개하지 않기 때문에 다른 객체가 접근할 수 없음 (⇒ 외부에 노출 시 캡슐화 위배, 응용프로그램의 확장성 및 신뢰도와 절충됨)

1) 메멘토(Memento) 객체에 원조본(Originator) 객체가 가진 내부 상태의 스냅샷을 저장

2) 원조본에서 복구, 실행 취소를 할때 메멘토에게 이전 상태를 요청할 수 있음

  • 메멘토에 정보를 저장하고 검색할 수 있는 것은 원조본 뿐 (=메멘토는 이외의 객체에게 보이지 않음)

활용성

  • 어떤 객체의 상태에 대한 스냅샷을 저장한 후 나중에 이전 상태로 복구해야 할 때
  • 상태를 얻는 데 필요한 직접적인 인터페이스를 두면 그 객체의 구현 세부사항이 드러날 수밖에 없고 이로 인해 캡슐화가 깨질 때

구조

참여자

  • Originator(ConstraintSolver) : 원조본 객체로 메멘토를 생성하여 현재 객체의 상태를 저장하고 메멘토를 사용하여 이전 상태로 복원
  • Memento(SoleverState) : 원조본 객체의 내부 상태를 필요한 만큼 저장하고 원조본을 제외한 객체는 접근할 수 없도록 막음
  • Caretaker(실행 취소 메커니즘/보관자) : 메멘토의 보관을 책임지며 메멘토의 내용을 검사하거나 내용을 건드리지 않음

예제

package memento;

public class Originator {
	
	private String state;

	public String getState() {
		return state;
	}

	public void setState(String state) {
		this.state = state;
	}
	
	public Memento saveStateToMemento() {
		return new Memento(state);
	}
	
	public void getStateFromMemento(Memento memento) {
		state = memento.getState();
	}
	

}
package memento;

public class Memento {
	
	private String state;
	
	public Memento(String state) {
		this.state = state;
	}
	
	public String getState() {
		return state;
	}

}
package memento;

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

public class CareTaker {
	
	private List<Memento> mementoList = new ArrayList<Memento>();
	
	public void add(Memento state) {
		mementoList.add(state);
	}
	
	public Memento get(int index) {
		return mementoList.get(index);
	}

}
package memento;

public class Main {

	public static void main(String[] args) {
		Originator originator = new Originator();
		CareTaker careTaker = new CareTaker();
		
		originator.setState("state #1");
		originator.setState("state #2");
		careTaker.add(originator.saveStateToMemento());
		originator.setState("state #3");
		careTaker.add(originator.saveStateToMemento());
		originator.setState("state #4");
	
		System.out.println("Current State : " + originator.getState());
		originator.getStateFromMemento(careTaker.get(0));
		System.out.println("First saved State : " + originator.getState());
		originator.getStateFromMemento(careTaker.get(1));
		System.out.println("Second saved State : " + originator.getState());
	}	
}
Current State : state #4
First saved State : state #2
Second saved State : state #3

협력 방법

  • 보관자 객체는 원조본 객체에 메멘토 객체를 요청하고 요청 시간 저장 후 원조본에게 메멘토를 돌려줌
  • 원조본 객체가 이전 상태로 돌아갈 필요가 없을 때는 전달할 필요가 없기 때문에 원조본에게 저장하지 않음

결과

  • 캡슐화된 경계 유지 : 원조본만 메멘토를 다룰 수 있어 메멘토가 외부로 노출되지 않음
  • Originator 클래스를 단순화할 수 있음 : 다른 방법으로 캡슐화를 유지하는 설계 방법에서는 Originator가 모든 버전의 내부 상태를 저장해야 사용자의 요청에 대응할 수 있으나 상태를 별도로 관리함으로써 Originator 클래스가 간단해짐
  • 메멘토의 사용으로 더 많은 비용이 들어갈 수 있음 : Originator 클래스가 많은 양의 정보를 저장해야 할 때나 상당히 자주 메멘토를 반환해야 하면 상당한 오버헤드를 가져올 수 있음 상태 보호 비용과 상태 복구의 비용이 적당하지 않으면 메멘토 패턴은 적합하지 않음
  • 제한 범위 인터페이스와 광범위 인터페이스를 정의해야 함 : 어떤 프로그래밍 언어에서는 원조본 객체만 메멘토의 상태에 접근할 수 있도록 보장하기가 어려움

⇒ 제한범위 인터페이스 : 다른 객체들에게 제공할 서비스 정의(Public만 접근 가능)

⇒ 광범위 인터페이스 Originator 클래스에게 제공하는 서비스

  • 메멘토를 관리하는 데 필요한 비용이 숨어있음 : 보관자 객체는 자신이 보관하는 메멘토를 삭제할 책임이 있으나 얼마나 많은 상태가 메멘토에 저장되었는지 알 방법이 없음 그러므로 보관자 객체가 가벼워도 메멘토를 저장할 때 저장 비용이 적지 않을 수 있음
  • 메멘토는 원조본의 내부 상태 변경 과정을 지속적으로 저장하되 변경된 정보들만 추가해 나감 (모든 객체 전체가 아니라 각 명령어가 수행되어 변경이 발생한 부분)

사용예

  • 자바 직렬화, 역직렬화 : 객체 <=> 바이트스트림

관련 패턴

  • 명령 패턴에서 실행 취소가 가능한 연산의 상태를 저장할 때 메멘토 패턴 사용할 수 있음
  • 반복자 패턴에서 반복 과정 상태를 관리할 때 메멘토 패턴 사용할 수 있음
profile
안녕하세요. 자바를 좋아하고 디자인 패턴, Refactoring, Clean Code에 관심이 많은 백엔드 개발자입니다.

0개의 댓글