애플사이다의 iOS 개발 일지

[디자인 패턴] Memento Pattern - 실행취소 기능이 필요할 때 본문

프로그래밍 철학

[디자인 패턴] Memento Pattern - 실행취소 기능이 필요할 때

Applecider 2022. 3. 23. 21:11

안녕하세요. 애플사이다입니다.

디자인 패턴 중 하나인 Memento Pattern (메멘토 패턴)을 알아보겠습니다.


Memento란?

단기 기억상실증을 앓는 주인공이 등장하는 <메멘토>라는 영화나

"Memento Mori" (죽음을 기억하라)라는 표현으로 접해본 단어다.

 

Memento는 "기억을 상기시켜주는 Reminder 역할을 하는 것"을 의미한다.

Cambridge 사전 검색결과

Memento Pattern이란?

메멘토 패턴은 객체를 변경 이전 상태로 복원할 수 있는 패턴이다.

기억 (백업해둔 데이터)을 바탕으로 과거로 돌아갈 수 있다는 점을 살려 네이밍한 것 같다.

 

Originator, Memento, CareTaker 3개 요소로 구성된다.

행동 패턴에 속하며, 기념품 패턴이라고도 부른다.

*행동 패턴 : 객체 간 알고리즘이나 책임 분배에 관련된 패턴

Memento Pattern 구조

  • Originator
    • Memento 객체를 생성하여 자신의 현재 State를 저장한다.
    • State를 복원할 때 Memento를 사용한다.
  • Memento
    • Originator의 State를 스냅샷 형태로 가진다.
    • 외부 객체가 Originator에 접근하는 것을 막는다.
    • Memento를 immutable하게 만들고, 생성자 주입을 통해 Originator의 데이터를 한 번만 전달받는다.
  • CareTaker
    • Memento 객체를 저장하여 Originator의 State History를 가진다.
    • Memento의 내용을 조작하지 않는다.

시간적 순서에 따라 Sequence Diagram으로 나타내 보면 아래와 같다.

Memento Pattern의&nbsp;Sequence Diagram

Memento Pattern을 사용할까?

실행취소 기능 (Undo)을 구현하거나, 오류가 발생한 경우 특정 객체를 복구할 때 필요하다.

이때 객체의 State (상태)를 기록해야 하는데, 외부에 저장하거나 외부에서 접근 가능하도록 하면 캡슐화를 위반하게 된다.

이러한 상황에서 메멘토 패턴을 활용하면, State를 저장할 객체를 캡슐화하고 외부에서의 접근을 제한할 수 있다.

 

위에서 나열한 내용을 다시 정리해보면,메멘토 패턴에서 Originator는 자신의 State를 기록하고, 이것을 Memento에 저장한다.Memento는 Originator 이외의 객체가 접근할 수 없도록 한다.Memento는 CareTaker에 저장되며, CareTaker의 제한된 인터페이스를 통해 Memento를 관리하므로 임의로 Memento의 내용을 조작할 수 없다.Originator는 Memento에 자유롭게 접근하여 원하는 State로 복원할 수 있다.

Memento Pattern의 특징

장점

  • 복구 기능을 쉽게 구현할 수 있다.
  • 캡슐화를 위반하지 않고 객체의 State 스냅샷을 생성할 수 있다.
  • 핵심 객체와 다른 별도의 객체에 데이터를 백업할 수 있어 안전하다.
  • CareTaker가 State 기록을 보관하는 역할을 담당하므로 Originator가 비대해지지 않는다.

단점

  • Memento 객체를 많이 생성할수록 메모리 소비가 크다.
  • Originator의 데이터 크기가 큰 경우 메모리 소비가 크다.
  • CareTaker가 오래된 Memento 객체를 삭제하도록 하려면, Originator의 생명주기를 추적해야 한다.

예제 코드

게임을 Save 및 Load하는 기능을 메멘토 패턴으로 잘 구현한 예제가 있어서 소개한다.

- Reference : Pingu님 블로그

1. Originator (Game)

class Game {
    var level: Int = 0
    var score: Int = 0
    
    func setLevel(level: Int) {
        self.level = level
    }
    
    func setScore(score: Int) {
        self.score = score
    }
    
    // Memento 생성 - Memento 생성자 주입으로 Originator 자체를 전달
    func createMemento() -> SaveData {
        print("Level : \(self.level), Score: \(self.score) 상태를 저장합니다.\n")
        return SaveData(originator: self)
    }
    
    func printCurrentState() {
        print("현재 상태 Level : \(self.level), Score: \(self.score)")
    }
}

2. Memento (SaveData)

class SaveData {
    private var originator: Game
    private var level: Int = 0
    private var score: Int = 0
    
    // Memento 생성 시 Originator의 State를 전달받음 (이후 수정하지 않음)
    init(originator: Game) {
        self.originator = originator
        self.level = originator.level
        self.score = originator.score
    }
    
    // Originator의 State를 복원
    func recover() {
        self.originator.setLevel(level: self.level)
        self.originator.setScore(score: self.score)
    }
}

3. CareTaker (GameDataSystem)

class GameDataSystem {
    private var history: [SaveData] = []
    
    func save(memento: SaveData) {  // Memento 스냅샷들을 저장
        self.history.append(memento)
    }
    
    func recoverLastState() {  // 가장 최근의 스냅샷을 복원
        if let mementoSnapshot: SaveData = self.history.popLast() {
            print("최근 저장 상태를 불러옵니다.\n")
            mementoSnapshot.recover()
        } else {
            print("저장 기록이 없습니다.\n")
        }
    }
}

4. 사용부 (Main.swift)

let originator = Game()
let caretaker = GameDataSystem()
originator.setLevel(level: 5)
originator.setScore(score: 3)

// Originator가 Memento 스냅샷을 생성
let memento = originator.createMemento()
// Memento 스냅샷을 CareTaker에 저장
caretaker.save(memento: memento) // 출력 - Level : 5, Score: 3 상태를 저장합니다.

// 게임을 하다가 레벨과 점수가 이전 저장 했을 때 보다 낮아짐
originator.setLevel(level: 1)
originator.setScore(score: 1)
originator.printCurrentState() // 출력 - 현재 상태 Level : 1, Score: 1

// 레벨과 점수 복구
caretaker.recoverLastState() // 출력 - 최근 저장 상태를 불러옵니다.
originator.printCurrentState() // 출력 - 현재 상태 Level : 5, Score: 3

caretaker.recoverLastState() // 출력 - 저장 기록이 없습니다.

 

- Reference

 

🍎 포스트가 도움이 되었다면, 공감🤍 / 구독🍹 / 공유🔗 / 댓글✏️ 로 응원해주세요. 감사합니다.

Comments