Memento Pattern in Swift

Độ khó: Beginner | Easy | Normal | Challenging
Xcode 14.0.1 | Swift 5.7


Giới thiệu 👋

Trong bài viết này mình sẽ giới thiệu đến các bạn một Fundamental Design Pattern - Memento Pattern. Vậy Memento Pattern là một pattern như thế nào, ứng dụng và cách cài đặt nó như thế nào?


Như thường lệ, mình sẽ không nói về lý thuyết/định nghĩa ngay. Mình sẽ trình bày một ví dụ về Memento Pattern trước, sau đó chúng ta sẽ đi sâu vào xem bản chất của nó nhé !!!

Đặt vấn đề 🤔

Mình có một đoạn code như sau:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class BankAccount {
    private(set) var balance: Double = 0.0

    func setBalance(balance: Double) {
        self.balance = balance
    }
}

class BankCustomer {
    let account: BankAccount

    init(account: BankAccount) {
        self.account = account
    }

    func withdraw(amount: Double) {
        account.setBalance(balance: account.balance - amount)
    }

    func deposit(amount: Double) {
        account.setBalance(balance: account.balance + amount)
    }
}
  • Trong đoạn code trên, mình có 2 class là BankCustomerBankAccount. Mỗi khách hàng sẽ có một tài khoản ngân hàng và số dư riêng. Dễ hiểu chứ nhỉ?
  • Khi chúng ta rút tiền thì hàm withdraw(amount:) sẽ được gọi đến.
  • Mọi thứ trông có vẻ ổn? Vậy vấn đề ở đây là gì nhỉ?
  • Giả sử mình muốn chuyển tiền đến cho người thân, sau khi hệ thống gọi đến hàm withdraw(amount:) và số dư của mình đã bị trừ đi. Tuy nhiên mình đã nhập sai số tài khoản của người thân, từ đây hệ thống phải trả lại số tiền mình đã gửi cho mình đúng không nhỉ? Vậy là code phía trên chưa làm được việc restore lại số dư cho mình rồi
  • Hoặc giả sử mình chỉ có 50 đồng trong tài khoản nhưng mình muốn rút đến 100 đồng. Sau khi hệ thống trừ 100 đồng thì số dư của mình không đủ. Vậy giao dịch rút 100 đồng đó là không hợp lệ, nên hệ thống cần phải restore lại số tiền là 50 đồng cho tài khoản của mình.

=> Đó cũng là lý do chúng ta có Memento Pattern để giúp restore lại trạng thái trước đó của một object và vẫn không vi phạm nguyên lý đóng gói (Encapsulation)
=> Cùng mình áp dụng Memento Pattern vào example code phía trên nhé

Ví dụ về Memento Pattern 😎

Sau đây là ví dụ về Memento Pattern của 2 class BankCustomerBankAccount :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
protocol Originator {
    func save() -> Memento
    func restore(memento: Memento)
}

class BankAccount: Originator {
    private(set) var balance: Double = 0.0

    func setBalance(balance: Double) {
        self.balance = balance
    }

    func save() -> Memento {
        return Memento(balance: balance)
    }

    func restore(memento: Memento) {
        balance = memento.balance
    }
}

class Memento {
    let balance: Double

    init(balance: Double) {
        self.balance = balance
    }
}

class BankCustomer {
    let account: BankAccount

    init(account: BankAccount) {
        self.account = account
    }

    func withdraw(amount: Double) {
        let memento = account.save()
        account.setBalance(balance: account.balance - amount)
        if account.balance < 0 {
            account.restore(memento: memento)
            print("Insufficient funds.")
        }
    }

    func deposit(amount: Double) {
        account.setBalance(balance: account.balance + amount)
    }
}
  • Có thể hơi khó hiểu phải không mọi người 😁. Tuy nhiên đừng lo lắng, chúng ta sẽ tìm hiểu từng thành phần trong Memento Pattern ngay sau đây.
  • Trước đó hãy thử chạy phần code có sử dụng Memento Pattern trước nhé.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
let account = BankAccount()
let customer = BankCustomer(account: account)
print("Balance: \(customer.account.balance)")     // Balance: 0.0

print("Deposit 100")                              // Deposit 100
customer.deposit(amount: 100)
print("Balance: \(customer.account.balance)")     // Balance: 100.0

print("Withdraw 50")                              // Withdraw 50
customer.withdraw(amount: 50)
print("Balance: \(customer.account.balance)")     // Balance: 50.0

print("Withdraw 100")                             // Withdraw 100
customer.withdraw(amount: 100)                    // Insufficient funds.
print("Balance: \(customer.account.balance)")     // Balance: 50.0

=> Ở lần withdraw cuối cùng, số dư (balance) trong tài khoản của mình chỉ còn 50 đồng. Tuy nhiên mình lại thực hiện lệnh rút 100 đồng. Nên hệ thống sẽ in ra thông báo: “Insufficient funds”, và restore lại số dư là 50 đồng cho mình
=> Đã đúng mục đích của chúng ta nhỉ. Tuyệt vời, phải không?


Vậy là chúng ta đã xem qua một ví dụ về việc sử dụng Memento Pattern rồi. Chúng ta sẽ đến với phần định nghĩa / lý thuyết của Memento Pattern nhé

Memento Pattern là gì? 🤔

  • Memento Pattern thuộc nhóm Behavioral Design Pattern. Bởi vì pattern này là pattern nói về hành động lưu và khôi phục dữ liệu. Nó sẽ giúp chúng ta lưu và khôi phục trạng thái trước đó của một object mà không làm tiết lộ cách mà chúng ta lưu hoặc lấy lại.
    • Memento is a behavioral design pattern that lets you save and restore the previous state of an object without revealing the details of its implementation. - Dive Into Design Pattern

Đó là một số định nghĩa của Memento Pattern. Chúng ta có sơ đồ như sau: Memento_Diagram.png

  • Memento Pattern nó sẽ gồm 3 phần:
    • Originator: Là một class mà chúng có thể lưu (chúng ta hay gọi là snapshot) lại trạng thái của nó, hoặc khôi phục (restore) trạng thái nếu cần. Ở ví dụ mục 2, BankAccountOriginator của chúng ta.
    • Memento: Là nơi chứa những property mà chúng ta cần lưu lại. Chúng ta nên cho class Memento này immutable (không thể thay đổi) và chỉ truyền data 1 lần duy nhất thông qua hàm khởi tạo (init). Ở ví dụ mục 2, Memento của chúng ta cần lưu lại balance
    • Care Taker: Là nơi lưu và khôi phục Memento từ Originator. Care Taker sẽ biết được lúc nào và tại sao (when and why) cần lưu lại giá trị gốc và khi nào thì cần được khôi phục giá trị gốc.

Khi nào thì sử dụng Memento Pattern? ⏳️

  • Như đã nói ở trên, chúng ta sử dụng Memento Pattern để lưu và khôi phục trạng thái của một object/class nào đó. Ngoài ra nó còn giúp chúng ta không vi phạm nguyên tắc bao đóng (Encapsulation)
    • Use the memento pattern whenever you want to save and later restore an object’s state. - Design Pattern by Tutorials
    • Use the memento pattern when you want to produce snap-shots of the object’s state to be able to restore a previous state of the object - Dive Into Design Pattern
    • Use the pattern when direct access to the object’s fields/get- ters/setters violates its encapsulation. - Dive Into Design Pattern

Ưu và nhược điểm 🤙

Ưu điểm

  • Giúp chúng ta lưu và khôi phục trạng thái trước đó của object/class
  • Giúp chúng ta không bị vi phạm nguyên tắc bao đóng (Encapsulation)

Nhược điểm

  • Ứng dụng có thể sử dụng rất nhiều bộ nhớ bởi vì người dùng có thể tạo Memento liên tục (ví dụ chúng ta lưu lại snapshot quá nhiều chẳng hạn)
  • “Caretakers should track the originator’s lifecycle to be able to destroy obsolete mementos.” - Dive Into Design Pattern

Kết luận 📔

  • Vậy là chúng ta đã tìm hiểu xong Memento Pattern là gì và cách cài đặt nó như thế nào thông qua blog này.

Một số ví dụ hay các bạn có thể tham khảo:

Reference 🥳

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy