스위프트로 작성된 메멘토
메멘토 패턴은 행동 디자인 패턴입니다. 이 패턴은 객체 상태의 스냅숏을 만든 후 나중에 복원할 수 있도록 합니다.
메멘토는 함께 작동하는 객체의 내부 구조와 스냅숏들 내부에 보관된 데이터를 손상하지 않습니다.
복잡도:
인기도:
사용 사례들: 메멘토의 원칙은 직렬화를 사용하여 달성할 수 있으며, 이는 스위프트 코드에서 매우 일반적입니다. 직렬화는 객체 상태의 스냅숏을 만드는 유일한 또는 가장 효율적인 방법은 아니나 다른 객체로부터 오리지네이터의 구조를 보호하면서 상태 백업을 저장할 수 있도록 합니다.
개념적인 예시
이 예시는 메멘토 패턴의 구조를 보여주고 다음 질문에 중점을 둡니다:
- 패턴은 어떤 클래스들로 구성되어 있나요?
 - 이 클래스들은 어떤 역할을 하나요?
 - 패턴의 요소들은 어떻게 서로 연관되어 있나요?
 
이 패턴의 구조를 배우면 실제 스위프트 사용 사례를 기반으로 하는 다음 예시를 더욱 쉽게 이해할 수 있을 것입니다.
Example.swift: 개념적인 예시
import XCTest /// The Originator holds some important state that may change over time. It also /// defines a method for saving the state inside a memento and another method /// for restoring the state from it. class Originator { /// For the sake of simplicity, the originator's state is stored inside a /// single variable. private var state: String init(state: String) { self.state = state print("Originator: My initial state is: \(state)") } /// The Originator's business logic may affect its internal state. /// Therefore, the client should backup the state before launching methods /// of the business logic via the save() method. func doSomething() { print("Originator: I'm doing something important.") state = generateRandomString() print("Originator: and my state has changed to: \(state)") } private func generateRandomString() -> String { return String(UUID().uuidString.suffix(4)) } /// Saves the current state inside a memento. func save() -> Memento { return ConcreteMemento(state: state) } /// Restores the Originator's state from a memento object. func restore(memento: Memento) { guard let memento = memento as? ConcreteMemento else { return } self.state = memento.state print("Originator: My state has changed to: \(state)") } } /// The Memento interface provides a way to retrieve the memento's metadata, /// such as creation date or name. However, it doesn't expose the Originator's /// state. protocol Memento { var name: String { get } var date: Date { get } } /// The Concrete Memento contains the infrastructure for storing the /// Originator's state. class ConcreteMemento: Memento { /// The Originator uses this method when restoring its state. private(set) var state: String private(set) var date: Date init(state: String) { self.state = state self.date = Date() } /// The rest of the methods are used by the Caretaker to display metadata. var name: String { return state + " " + date.description.suffix(14).prefix(8) } } /// The Caretaker doesn't depend on the Concrete Memento class. Therefore, it /// doesn't have access to the originator's state, stored inside the memento. It /// works with all mementos via the base Memento interface. class Caretaker { private lazy var mementos = [Memento]() private var originator: Originator init(originator: Originator) { self.originator = originator } func backup() { print("\nCaretaker: Saving Originator's state...\n") mementos.append(originator.save()) } func undo() { guard !mementos.isEmpty else { return } let removedMemento = mementos.removeLast() print("Caretaker: Restoring state to: " + removedMemento.name) originator.restore(memento: removedMemento) } func showHistory() { print("Caretaker: Here's the list of mementos:\n") mementos.forEach({ print($0.name) }) } } /// Let's see how it all works together. class MementoConceptual: XCTestCase { func testMementoConceptual() { let originator = Originator(state: "Super-duper-super-puper-super.") let caretaker = Caretaker(originator: originator) caretaker.backup() originator.doSomething() caretaker.backup() originator.doSomething() caretaker.backup() originator.doSomething() print("\n") caretaker.showHistory() print("\nClient: Now, let's rollback!\n\n") caretaker.undo() print("\nClient: Once more!\n\n") caretaker.undo() } }  Output.txt: 실행 결과
Originator: My initial state is: Super-duper-super-puper-super. Caretaker: Saving Originator's state... Originator: I'm doing something important. Originator: and my state has changed to: 1923 Caretaker: Saving Originator's state... Originator: I'm doing something important. Originator: and my state has changed to: 74FB Caretaker: Saving Originator's state... Originator: I'm doing something important. Originator: and my state has changed to: 3681 Caretaker: Here's the list of mementos: Super-duper-super-puper-super. 11:45:44 1923 11:45:44 74FB 11:45:44 Client: Now, let's rollback! Caretaker: Restoring state to: 74FB 11:45:44 Originator: My state has changed to: 74FB Client: Once more! Caretaker: Restoring state to: 1923 11:45:44 Originator: My state has changed to: 1923  실제 사례 예시
Example.swift: 실제 사례 예시
import XCTest class MementoRealWorld: XCTestCase { /// State and Command are often used together when the previous state of the /// object should be restored in case of failure of some operation. /// /// Note: UndoManager can be used as an alternative. func test() { let textView = UITextView() let undoStack = UndoStack(textView) textView.text = "First Change" undoStack.save() textView.text = "Second Change" undoStack.save() textView.text = (textView.text ?? "") + " & Third Change" textView.textColor = .red undoStack.save() print(undoStack) print("Client: Perform Undo operation 2 times\n") undoStack.undo() undoStack.undo() print(undoStack) } } class UndoStack: CustomStringConvertible { private lazy var mementos = [Memento]() private let textView: UITextView init(_ textView: UITextView) { self.textView = textView } func save() { mementos.append(textView.memento) } func undo() { guard !mementos.isEmpty else { return } textView.restore(with: mementos.removeLast()) } var description: String { return mementos.reduce("", { $0 + $1.description }) } } protocol Memento: CustomStringConvertible { var text: String { get } var date: Date { get } } extension UITextView { var memento: Memento { return TextViewMemento(text: text, textColor: textColor, selectedRange: selectedRange) } func restore(with memento: Memento) { guard let textViewMemento = memento as? TextViewMemento else { return } text = textViewMemento.text textColor = textViewMemento.textColor selectedRange = textViewMemento.selectedRange } struct TextViewMemento: Memento { let text: String let date = Date() let textColor: UIColor? let selectedRange: NSRange var description: String { let time = Calendar.current.dateComponents([.hour, .minute, .second, .nanosecond], from: date) let color = String(describing: textColor) return "Text: \(text)\n" + "Date: \(time.description)\n" + "Color: \(color)\n" + "Range: \(selectedRange)\n\n" } } }  Output.txt: 실행 결과
Text: First Change Date: hour: 12 minute: 21 second: 50 nanosecond: 821737051 isLeapMonth: false Color: nil Range: {12, 0} Text: Second Change Date: hour: 12 minute: 21 second: 50 nanosecond: 826483011 isLeapMonth: false Color: nil Range: {13, 0} Text: Second Change & Third Change Date: hour: 12 minute: 21 second: 50 nanosecond: 829187035 isLeapMonth: false Color: Optional(UIExtendedSRGBColorSpace 1 0 0 1) Range: {28, 0} Client: Perform Undo operation 2 times Text: First Change Date: hour: 12 minute: 21 second: 50 nanosecond: 821737051 isLeapMonth: false Color: nil Range: {12, 0}