Skip to main content

Swift Example

/// Memento Design Pattern
///
/// Intent: Lets you save and restore the previous state of an object without
/// revealing the details of its implementation.

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