Memento Pattern
Last updated
Last updated
Bài viết được sao chép từ , tác giả: Hoàng Đinh
Phân loại: Behavior Pattern
Mục đích: Memento cho phép người lưu trữ và hồi phục các phiên bản cũ của 1 object mà không can thiệp vào nội dung của object đó.
Tưởng tượng bạn đang tạo 1 text editor. Bao gồm các chức năng như chỉnh sửa text, format text, thêm ảnh, v.v..
Để phát triển thêm app, bạn quyết định cho phép người dùng undo và redo bất kỳ thao tác nào thực hiện trên tệp văn bản. Bằng cách trước khi thực hiện bất kì thao tác nào, app sẽ lưu state tất cả object vào trong một storage (take snapshot, lưu vào history). Sau đó, khi user cần undo 1 thao tác, app lấy state đã được lưu trước đó trong storage và dùng nó để restore state của tất cả object.
Nhưng cái khó ở đây là, làm thế nào để thật sự take snapshot? Bạn sẽ phải cần duyệt qua tất cả field của object để lưu nó vào storage. Tuy nhiên, việc đó là không khả thi vì thực tế hầu hết các objects thường giấu phần lớn data trong các trường private.
Có vẻ như chỉ để take snapshot, ta đã đưa app vào một tình thế rất gian nan: ta public tất cả các private fields của editor object khiến nó trở nên mong manh và tạo ra 1 class chuyên để copy editor object luôn phải thay đổi mỗi khi editor object thay đổi. Vậy còn cách nào khác để triển khai undo redo không?
Memento pattern giao việc tạo ra snapshot cho chính chủ nhân của state đó (originator object). Chính object đó sẽ dễ dàng tạo ra snapshot vì nó có toàn quyền truy cập state của nó.
Memento gợi ý ta nên lưu state được copy từ object vào một object gọi là memento. Content của memento object không được truy cập từ các object khác ngoại trừ originator object. Các object khác phải giao tiếp với memento thông qua interface bị giới hạn chỉ cho phép lấy metadata của snapshot (metadata - những data về data chứ không phải là data: ngày tạo, tên action, v.v.)..
Các thành phần trong mô hình:
Originator: Là class sản xuất ra snapshots từ các state của chính nó, đồng thời restore state từ snapshots khi cần.
Memento: Là object lưu giá trị, được xem như là một snapshot của Originator. Trong thực tiễn nó là immutable class (class không thay đổi được) và truyền data vào 1 lần duy nhất khi construct.
Caretaker: Giữ câu trả lời cho các câu hỏi "khi nào" và "vì sao" cho những thời điểm capture lại state của Originator và lúc restore lại state. Caretaker lưu trữ 1 stack các mementos. Khi Originator cần đi lùi về history, Caretaker lấy memento trên cùng của stack và truyền vào restore method của Originator.
Với cách triển khai này, Memento được lồng bên trong Originator. Giúp Originator truy cập private fields và methods của memento. Còn Caretaker bị giới hạn việc truy cập memento, cho phép nó lưu các mementos thành 1 stack nhưng không đụng gì đến các state.
Ưu điểm
Bảo bảo nguyên tắc đóng gói: sử dụng trực tiếp trạng thái của đối tượng có thể làm lộ thông tin chi tiết bên trong đối tượng và vi phạm nguyên tắc đóng gói.
Đơn giản code của Originator bằng cách để Memento lưu giữ trạng thái của Originator và Caretaker quản lý lịch sử thay đổi của Originator.
Một số vấn đề cần xem xét khi sử dụng Memento Pattern:
Khi có một số lượng lớn Memento được tạo ra có thể gặp vấn đề về bộ nhớ, performance của ứng dụng.
Khó đảm bảo trạng thái bên trong của Memento không bị thay đổi.
Nhược điểm:
App tiêu thụ nhiều RAM và xử lý nếu clients tạo mementos quá thường xuyên.
Caretakers phải theo dõi vòng đời của originator để có thể hủy các mementos không dùng nữa.
Hầu hết các ngôn ngữ hiện đại, hay cụ thể hơn là dynamic programming languages, ví dụ như PHP, Python và Javascript, không thể đảm bảo state bên trong memento được giữ không ai đụng tới.
Các ứng dụng cần chức năng cần Undo/Redo: lưu trạng thái của một đối tượng bên ngoài và có thể restore/rollback sau này.
Thích hợp với các ứng dụng cần quản lý transaction.
Có thể sử dụng Command và Memento cùng nhau khi thực hiện “hoàn tác”. Trong trường hợp này, các lệnh chịu trách nhiệm thực hiện các hoạt động khác nhau trên một đối tượng đích, trong khi các Memento lưu trạng thái của đối tượng đó ngay trước khi lệnh được thực thi.
Có thể sử dụng Memento cùng với Iterator để nắm bắt trạng thái lặp lại hiện tại và khôi phục nó nếu cần.
Đôi khi Prototype có thể là một giải pháp thay thế đơn giản hơn cho Memento. Điều này hoạt động nếu đối tượng, trạng thái mà bạn muốn lưu trữ trong lịch sử, khá đơn giản và không có liên kết đến tài nguyên bên ngoài hoặc các liên kết dễ thiết lập lại.
[1] Refactoring.Guru. https://refactoring.guru/design-patterns
[2] Design Patterns for Dummies, Steve Holzner, PhD
[3] Head First, Eric Freeman
[4] Gang of Four Design Patterns 4.0
[5] Dive into Design Pattern
Xem file