본문 바로가기
iOS/Design Pattern

Design Pattern - Memento Pattern

by HaningYa 2020. 8. 9.
728x90

Memento Pattern

 

메멘토 패턴 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 둘러보기로 가기 검색하러 가기 메멘토 패턴(memento pattern)은 객체를 이전 상태로 되돌릴 수 있는 기능을 제공하는 소프트웨어 디자인 패턴이다. (롤백을 통한 ��

ko.wikipedia.org

메멘토 패턴은 Object를 저장하거나 불러오게 할 수 있다. 3가지 부분으로 나눠진다.

Design_Pattern_by_tutorials (raywenderlinch)

  • Originator: 저장되어야 할 객체이다.
  • memento: 저장된 상태를 나타낸다.
  • CareTaker:
    - save: careTaker는 originator에게 저장을 요청하고 memento 로부터 응답을 받는다.
    - restore: memento를 유지하는 역할을 담당하며 추후에 originator state 를 복원하기 위해 originator 에게 memento 를 제공한다.

iOS Menento Pattern

  • iOS 앱은 전형적으로 Encoder 을 사용하여 originator의 state를 메멘토로 인코딩(save) 하고 Decoder를 사용하여 메멘토를 originator 로 다시 디코딩(restore) 한다.
  • 이것은 인코딩과 디코딩 로직이 다양한 Originators에 대해 재사용 되게 허용해 준다.
  • 예를들어 JSONEncoder 과 JSONDecoder는 객체를 JSON 데이터로 encode 나 decode 할 수 있다.

언제 사용해야 할까?

  • 객체의 상태를 저장했다가 나중에 복원하고 싶을때
  • 게임 저장 시스템에 사용할 수 있다. originator 는 gate state(level, health, number of lives)가 될 수 있고 memento는 저장된 데이터, careTaker는 게임 시스템으로 볼 수 있다.
  • memento들을 배열로 유지시켜 이전 상태들을 Stack 처럼 표현할 수 있다. 이를 통해 IDE 나 그래픽 소프트웨어에서 undo/redo 기능을 구현할 수 있다.

 

Memento Pattern - Behavior 패턴이다.

save 과 restoration 행위에 대한 패턴이기 때문에 Behavior 패턴에 속한다.


Memento Pattern - 예제 코드

  • 게임 프로퍼티를 들고있는 내부 State를 가지고 있는 Game 을 정의한다.
  • Game 은 게임 내의 활동들에 대한 메소드를 들고 있다.
  • Game 과 State를 Codable에 순응하게 선언해야 한다.
import Foundation

// Originator
public class Game: Codable {

	public class State: Codable {
		public var attemptsRemaining: Int = 3
    	public var level: Int = 1
    	public var score: Int = 0
	}

	public var state = State()

	public func rackUpMassivePoints() {
		state.score += 9002
	}

	public func monstersEatPlayer() {
		state.attemptsRemaining -= 1
    }
}
 

왜 Codable 에 순응하게 해야될까?

  • 애플은 Codable 을 Swift4 에 소개했다.
  • Codable 에 순응하는 타입은 애플이 말하자면 "convert itself into and out of an external representation" 이라고 한다.
  • 결국 자기 자신을 save 하고 restore 할 수 있는 타입을 뜻한다.
  • 바로 originator의 역할과 동일하다.

Game 과 State 가 Codable 에 순응하기 때문에 컴파일러는 자동으로 필요한 모든 Codable 프로토콜 메소드를 만들어 준다.

String, Int, Double 과 같은 Swift 가 제공하는 타입은 모두 Codable에 순응할 수 있다.

공식적으로 CodableEncodableDecodable 프로토콜을 합친 typealias 이다.

typealias Codable = Decodable & Encodable

Encodable  타입의 경우 Encoder 에 의해 외부 표현으로 변환될 수 있다.

외부 표현의 실제 타입은 사용하는 Encoder 의 종류에 따라 달라진다.

Foundation은 몇가지 기본 encoder 를 제공해 준다.

예를 들어 객체를 JSON 데이터로 변환해 주는 JSONEncoder 가 있다.


다시 예제 코드로 돌아가서

[originator]

import Foundation

// Originator
public class Game: Codable {

	public class State: Codable {
		public var attemptsRemaining: Int = 3
    	public var level: Int = 1
    	public var score: Int = 0
	}

	public var state = State()

	public func rackUpMassivePoints() {
		state.score += 9002
	}

	public func monstersEatPlayer() {
		state.attemptsRemaining -= 1
    }
}
 

[Memento]

typealias GameMemento = Data

[careTaker]

public class GameSystem {
	private let decoder = JSONDecoder()
    private let encoder = JSONEncoder()
    private let userDefaults = UserDefaults.standard
    
    public func save(_ game: Game, title: String) throws {
    	let data = try encoder.encode(game)
        userDefaults.set(data,forKey:title)
    }
    public func load(title: String) throws -> Game {
    	guard let data = userDefaults.data(forKey: title),
        let game = try? decoder.decode(Game.self, from: data)
        else {
        throw Error.gameNotFound
        }
        return game
    }
    
    public enum Error: String, Swift.Error {
    	case gameNotFound
    }
}

 

  1. decoder 과 encoder, userDefaults 에 대한 프로퍼티를 선언한다.
  2. decoder는 Games 를 Data 로 부터 decode 하고
    encoder 는 Games 를 Data 로 encode 하고
    userDefaults 는 Data 를 디스크에 저장한다.
  3. save(_:title:) 는 저장 로직을 캡슐화 시킨다. 먼저 encoder 을 통해 game을 저장한다. 이 작업은 에러를 발생 시킬 수 있기 때문에 try 로 예외처리를 해준다. 다음에 결과 데이터를 주어진 title 에 맞게 userDefaults 에 저장한다.
  4. load(title:) 도 마찬가지로 불러오는 로직을 캡슐화 한다. userDefaults 로 부터 데이터를 가지고 와서 decoder 를 통해 Data를 Game 으로 decode 한다. 만약 둘중 하나라도 작업이 실패할 경우 Error.gameNotFound 가 던져진다. 둘 모두 성공할 경우 결과값인 저장된 Game 을 받을 수 있다.

Memento Pattern 을 이용해 Game 상태를 저장하기 위한 준비는 끝났고

실제로 저장과 불러오는 코드를 작성해 보자

//게임이 진행되고 플레이어는 한번 먹히고 한번 포인트를 왕창 받는다.
var game = Game()
game.monstersEatPlayer()
game.rackUpMAssivePoints()

//게임 저장
let gameSystem = GameSystem()
try gameSystem.save(game, title: "Best Game Ever")

//새로운 게임
game = Game()
print("New Game Score: \(game.state.score)")
//New Game Score: 0

//게임 불러오기
game = try! gameSystem.load(title: "Best Game Ever")
print("Loaded Game Score: \(game.state.score)")
//Loaded Game Score: 9002


Memento Pattern 사용시 주의할 점

  • Codable 프로퍼티를 추가하거나 삭제할때 주의해야 한다.
    -> Encoding 과 Decoding 시 error 를 throw 할 수 있다. try! 처럼 강제로 unwrapping 하면 필요한 데이터가 없을 수 도 있고 그렇게 되면 앱이 뻗는다.
  • 위 문제를 완화하기 위해서는 try! 사용을 지양하고 모델을 바꿀때 먼저 계획을 잘 짜야한다.
  • 예를들어 모델들을 버젼관리 할 수 있다, 하지만 버젼 업그레이드의 경우 어떻게 다룰지 잘 생각해야한다.
    1) 이전 새로운 버전이 나올 때 마다 이전 데이터를 삭제한다.
    2) 새로운 업그레이드 path 를 만들어 old 데이터를 new 데이터로 바꾼다.
    3) 1,2번 방법을 조합하여 사용한다.

 

728x90

'iOS > Design Pattern' 카테고리의 다른 글

Design Pattern - Builder Pattern  (0) 2020.08.09
Design Pattern - Observer Pattern  (0) 2020.08.09
Design Pattern - Singleton Pattern  (0) 2020.08.09
Design Pattern - Strategy Pattern  (0) 2020.08.07
Design Pattern - Delegation Pattern  (0) 2020.08.06

댓글