반응형

의도

  • 캡슐화를 유지하면서 객체 내부 상태를 외부에 저장하는 패턴
    • 객체 상태를 외부에 저장했다가 해당 상태로 다시 복구할 수 있다.
  • 캡슐화를 위배하지 않은 채 어떤 객체의 내부 상태를 잡아내고 실체화시켜 둠으로써, 이후 해당 객체가 그 상태로 되돌아올 수 있도록 한다.

 

장점

  • 캡슐화를 지키면서 상태 객체 상태 스냅샷을 만들 수 있다.
    • 순간적인 상태를 보관해둔다.
  • 객체 상태 저장하고 또는 복원하는 역할을 CareTaker에게 위임할 수 있다.
  • 객체 상태가 바뀌어도 클라이언트 코드는 변경되지 않는다.

 

단점

  • 많은 정보를 저장하는 Mementor를 자주 생성하는 경우 메모리 사용량에 많은 영향을 줄 수 있다.

 

알려진 사용 예

  • 자바
    • 객체 직렬화, java.io.Serializable
      • Serializable 인터페이스를 구현해야 한다.
      • 객체 직렬화는 객체를 바이트스트림으로 변환하는 것을 의미한다.
      • 객체 역직렬화는 바이트스트림을 객체로 복원하는 과정을 의미한다.
    • java.util.Date

 

활용성

  • 어떤 객체의 상태에 대한 스냅샷을 저장한 후 나중에 이 상태로 복구해야 할 때
  • 상태를 얻는 데 필요한 직접적인 인터페이스를 두면 그 객체의 구현 세부사항이 드러날 수 밖에 없고, 이것으로 인해서 객체의 캡슐화가 깨질 때
    • 객체의 내부 구조가 변경되면(인터페이스의 구조 변경) 클라이언트가 객체의 세부사항에 의존하고 있기 때문에 같이 코드가 수정되어야 한다.
    • 예) 기존 사용하던 것이 필요없어지면 클라이언트 코드에서도 제거되어야 한다.
    • 예) 기존 사용하던 것이 추가적인 정보를 요구하여 인터페이스가 변경된 경우, 클라이언트 코드에서도 수정되어야 한다.

 

결과

  • 캡슐화된 경계를 유지할 수 있다.
    • 원조본만 메멘토를 다룰 수 있기 때문에 메멘토가 외부에 노출되지 않는다.
    • 이 패턴은 복잡한 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

코딩으로 학습하는 GoF의 디자인 패턴, 백기선

 

반응형

+ Recent posts