포스트

[Behavioral Pattern] 메멘토 패턴 (Memento Pattern)

Design Pattern / Behavioral Pattern

메멘토 패턴의 정의와 해당 디자인 패턴의 예제 코드를 통한 이해 및 설명 정리

배포된 예제 사이트

개념

  • 객체의 상태 정보를 가지는 클래스를 따로 생성하여 객체의 상태를 저장하거나, 이전 상태로 복원할 수 있게 해주는 패턴

  • 원하는 시점의 상태로 복원 가능

패턴 구조

memento

  • Originator

    • 내부 상태를 보유하고 있는 일부 객체

    • CareTakerOriginator에 대해 무언가를 하지만, 변경에 대한 실행 취소를 하기를 원함

  • CareTaker

    • 먼저 Originator에게 Memento 객체를 요청

    • 그 뒤, 예정된 일련의 명령을 수행

    • 명령 이전의 상태로 되돌리기 위해 Memento 객체를 Originator에 반환

  • Memento

    • 객체 자신은 불투명 자료형

      • CareTaker가 변경할 수 없거나 변경해서는 안됨

예제 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
class Originator {
  private state: string;

  constructor(state: string) {
    this.state = state;
    console.log(`Originator: My initail state is: ${state}`);
  }

  public doSomething(): void {
    console.log(`Originator: I\'m doing something important`);
    this.state = this.generateRandomString(30);
    console.log(`Originator: and my state has changed to: ${this.state}`);
  }

  private generateRandomString(length: number = 10): string {
    const charSet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

    return Array.apply(null, { length })
      .map(() => charSet.charAt(Math.floor(Math.random() * charSet.length)))
      .join("");
  }

  public save(): Memento {
    return new ConcreteMemento(this.state);
  }

  public restore(memento: Memento): void {
    this.state = memento.getState();
    console.log(`Originator: My state has changed to: ${this.state}`);
  }
}

interface Memento {
  getState(): string;
  getName(): string;
  getDate(): string;
}

class ConcreteMemento implements Memento {
  private state: string;
  private date: string;

  constructor(state: string) {
    this.state = state;
    this.date = new Date().toISOString().slice(0, 19).replace("T", "");
  }

  public getState(): string {
    return this.state;
  }

  public getName(): string {
    return `${this.date} / (${this.state.substring(0, 9)}...)`;
  }

  public getDate(): string {
    return this.date;
  }
}

class CareTaker {
  private mementos: Memento[] = [];
  private originator: Originator;

  constructor(originator: Originator) {
    this.originator = originator;
  }

  public backup(): void {
    console.log("\nCareTaker: Saving Originator's state...");
    this.mementos.push(this.originator.save());
  }

  public undo(): void {
    if (!this.mementos.length) return;
    const memento = this.mementos.pop() as Memento;

    console.log(`CareTaker: Restoring state to: ${memento?.getName()}`);
    this.originator.restore(memento);
  }

  public showHistory(): void {
    console.log("CareTaker: Here's the list of mementos:");

    for (const memento of this.mementos) {
      console.log(memento.getName());
    }
  }
}

/** Client Code */
const originator = new Originator("Super-duper-super-puper-super.");
const caretaker = new CareTaker(originator);

caretaker.backup();
originator.doSomething();

caretaker.backup();
originator.doSomething();

caretaker.backup();
originator.doSomething();

console.log("");
caretaker.showHistory();

console.log("\nClient: Now, let's rollback!\n");
caretaker.undo();

console.log("\nClient: Once more!\n");
caretaker.undo();

참고한 출처 사이트

Refactoring GURU

Inpa Dev Blog (디자인 패턴)

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.