iOS设计模式之备忘录

145 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第21天,点击查看活动详情

本文主要介绍iOS设计模式中的备忘录模式。备忘录顾名思义就是通过记录对象的状态,以便后面可以恢复保持到原先的状态。

1. 什么是备忘录模式

备忘录定义为“作为某人或某事的提醒物或纪念品而保留下来的物品”。类似我的即时贴,我们有的时候有想法了或者想要提醒一些事的时候写在即时贴上,后面这个信息用过了就可以扔到垃圾桶。
我们借用类似的思想,来保存对象的状态并在后来进行恢复。状态本身被创建为一种对象形式。它封装了原始对象的内部状态。只有创建即时贴的原始对象才能看懂保存的状态并用它恢复原来的状态。
在响应某些事件时,应用程序需要保存自身的状态,比如当用户保存文档或程序退出时。比如,退出游戏之前,可能需要保存当前会话的状态,比如游戏等级,敌人数量,可用武器等。再次打开的时候,玩家就可以从离开的地方接着玩,或者我们编辑文档的时候,我么可以通过撤销回到之前的状态,这些程序需要保存当前上下文的复杂状态的快照并在以后恢复。

备忘录模式:在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以改对象恢复到原先保存的状态。

2. 什么时候时候使用备忘录模式

满足下面的两个条件时,应当考虑使用这一模式:

  • 需要保存一个对象(或某部分)在某一个时刻的状态,这样以后就可以恢复到先前的状态
  • 用于获取状态的接口会暴漏实现的细节,需要将其隐藏起来。

3. 代码展示

备忘录模式主要有3个关键角色:原发器(Originator),备忘录(Memento),和看管人(Caretaker)。原发器创建一个包含其状态的备忘录,并传给看管人。看管人不知如何与备忘录交互,但会把备忘录放在安全之处保管好。当看管人请求原发器对象保存其状态时,原发器对象将使用内部状态创建一个新的备忘录实例。然后看管人保存备忘录对象。

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()
    }
}

执行结果

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

4. 小结

我们开发的过程中比如对象的归档和解档属性列表序列化核心数据中采用了备忘录模式。比如我们实现绘画的涂鸦或者文档编辑器经常会有撤销的操作,这就需要我们保存之前的操作状态使用备忘录模式。备忘录模式主要是状态的保存以及隐藏获取状态实现的细节。