
Swift 备忘录模式讲解和代码示例
备忘录是一种行为设计模式, 允许生成对象状态的快照并在以后将其还原。
备忘录不会影响它所处理的对象的内部结构, 也不会影响快照中保存的数据。
复杂度:
流行度:
使用示例: 备忘录的基本功能可用序列化来实现, 这在 Swift 语言中很常见。 尽管备忘录不是生成对象状态快照的唯一或最有效的方法, 但它能在保护原始对象的结构不暴露给其他对象的情况下保存对象状态的备份。
以下示例可在
Swift Playgrounds
上使用。
感谢
Alejandro Mohamad 创建了Playground版本。
概念示例
本例说明了备忘录设计模式的结构并重点回答了下面的问题:
- 它由哪些类组成?
- 这些类扮演了哪些角色?
- 模式中的各个元素会以何种方式相互关联?
了解该模式的结构后, 你可以更容易地理解下面基于真实世界的 Swift 应用案例。
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}