반응형
의도
- 캡슐화를 유지하면서 객체 내부 상태를 외부에 저장하는 패턴
- 객체 상태를 외부에 저장했다가 해당 상태로 다시 복구할 수 있다.
- 캡슐화를 위배하지 않은 채 어떤 객체의 내부 상태를 잡아내고 실체화시켜 둠으로써, 이후 해당 객체가 그 상태로 되돌아올 수 있도록 한다.
장점
- 캡슐화를 지키면서 상태 객체 상태 스냅샷을 만들 수 있다.
- 순간적인 상태를 보관해둔다.
- 객체 상태 저장하고 또는 복원하는 역할을 CareTaker에게 위임할 수 있다.
- 객체 상태가 바뀌어도 클라이언트 코드는 변경되지 않는다.
단점
- 많은 정보를 저장하는 Mementor를 자주 생성하는 경우 메모리 사용량에 많은 영향을 줄 수 있다.
알려진 사용 예
- 자바
- 객체 직렬화, java.io.Serializable
- Serializable 인터페이스를 구현해야 한다.
- 객체 직렬화는 객체를 바이트스트림으로 변환하는 것을 의미한다.
- 객체 역직렬화는 바이트스트림을 객체로 복원하는 과정을 의미한다.
- java.util.Date
- 객체 직렬화, java.io.Serializable
활용성
- 어떤 객체의 상태에 대한 스냅샷을 저장한 후 나중에 이 상태로 복구해야 할 때
- 상태를 얻는 데 필요한 직접적인 인터페이스를 두면 그 객체의 구현 세부사항이 드러날 수 밖에 없고, 이것으로 인해서 객체의 캡슐화가 깨질 때
- 객체의 내부 구조가 변경되면(인터페이스의 구조 변경) 클라이언트가 객체의 세부사항에 의존하고 있기 때문에 같이 코드가 수정되어야 한다.
- 예) 기존 사용하던 것이 필요없어지면 클라이언트 코드에서도 제거되어야 한다.
- 예) 기존 사용하던 것이 추가적인 정보를 요구하여 인터페이스가 변경된 경우, 클라이언트 코드에서도 수정되어야 한다.
결과
- 캡슐화된 경계를 유지할 수 있다.
- 원조본만 메멘토를 다룰 수 있기 때문에 메멘토가 외부에 노출되지 않는다.
- 이 패턴은 복잡한 Originator 클래스의 내부 상태를 다른 객체로 분리하는 방법으로 상태에 대한 정보의 캡슐화를 보장한다.
- Originator 클래스를 단순화할 수 있다.
- 다른 방법으로 캡슐화를 유지하는 설계 방법에서는 Originator가 다양한 버전의 내부 상태를 모두 저장해야 사용자의 요청에 대응할 수 있다.
- 메멘토 패턴을 사용하면 상태를 별도로 관리하기 때문에 Originator 클래스는 간단해진다.
- 메멘토의 사용으로 더 많은 비용이 들어갈 수도 있다.
- Originator 클래스가 많은 양의 정보를 저장해야 할 때나 메멘토를 자주 반환해야 할 때라면 메멘토가 상당한 오버헤드를 가져올 수 있다.
- Originator 클래스의 상태를 보호하는 비용과 상태 복구의 비용이 크다면 메멘토 패턴은 적합하지 않다.
- 메멘토를 관리하는 데 필요한 비용이 숨겨져있다.
- 보관자 객체는 자신이 보관하는 메멘토를 삭제할 책임이 있다. 그러나 보관자 쪽에서는 얼마나 많은 상태가 메멘토에 저장되었는지 알 방법이 없다.
- 보관자 객체가 아무리 가볍다 해도 메멘토를 저장할 때 적지 않은 저장 비용을 유발할 수 있다.
협력 방법
- 보관자(Caretaker) 객체는 원조본(Originator) 객체에 메멘토 객체를 요청한다. 또 요청한 시간을 저장하며, 받은 메멘토 객체를 다시 원조본 객체에게 돌려준다.
- 원조본 객체가 이전 상태로 돌아갈 필요가 없는 경우, 보관자 객체는 메멘토 객체를 원조본 객체에 전달하지 않을 수도 있다.
- 메멘토 객체는 수동적이다. 메멘토 객체를 생성한 원조본 객체만이 상태를 설정하고 읽어올 수 있다.
- 메멘토 객체는 불변 객체이다.
구조
실제 구현 구조
소스코드
//Originator
public class Game {
private int redTeamScore;
private int blueTeamScore;
public int getRedTeamScore() {
return redTeamScore;
}
public void setRedTeamScore(int redTeamScore) {
this.redTeamScore = redTeamScore;
}
public int getBlueTeamScore() {
return blueTeamScore;
}
public void setBlueTeamScore(int blueTeamScore) {
this.blueTeamScore = blueTeamScore;
}
public GameSave save() {
return new GameSave(this.blueTeamScore, this.redTeamScore);
}
public void restore(GameSave gameSave) {
this.blueTeamScore = gameSave.getBlueTeamScore();
this.redTeamScore = gameSave.getRedTeamScore();
}
}
//Memento
public final class GameSave {
private final int blueTeamScore;
private final int redTeamScore;
public GameSave(int blueTeamScore, int redTeamScore) {
this.blueTeamScore = blueTeamScore;
this.redTeamScore = redTeamScore;
}
public int getBlueTeamScore() {
return blueTeamScore;
}
public int getRedTeamScore() {
return redTeamScore;
}
}
//Caretaker
public class Client {
public static void main(String[] args) {
Game game = new Game();
game.setBlueTeamScore(10);
game.setRedTeamScore(20);
GameSave prevSavePoint = game.save();
game.setBlueTeamScore(12);
game.setRedTeamScore(22);
GameSave afterSavePoint = game.save();
game.restore(prevSavePoint);
System.out.println(game.getBlueTeamScore());
System.out.println(game.getRedTeamScore());
System.out.println("==============================");
game.restore(afterSavePoint);
System.out.println(game.getBlueTeamScore());
System.out.println(game.getRedTeamScore());
}
}
관련 패턴
- 명령 패턴은 실행 취소가 가능한 연산의 상태를 저장할 때 메멘토 패턴을 사용할 수 있다.
- 메멘토 패턴은 반복자 패턴에서의 반복 과정 상태를 관리할 수 있다.
[참고자료]
리처드 헬름, 랄프 존슨, 존 블리시디스, 『GoF의 디자인 패턴 : 재사용성을 지닌 객체지향 소프트웨어의 핵심요소』, 김정아 번역, 프로텍미디어(2015)
http://www.cs.unc.edu/~stotts/GOF/hires/pat5ffso.htm
반응형