I. Giới thiệu:
Memento là một Design Pattern thuộc loại Behavior. Nó cho phép chúng ta lưu trữ và khôi phục trạng thái của một đối tượng mà không tiết lộ chi tiết bên trong của nó.
Memento Pattern gồm các thành phần chính như sau:
- Originator: là Object có trạng thái được lưu trữ hoặc khôi phục.
- Mementor: là trạng thái (State) của Object khi đang được lưu trữ.
- CareTaker: đóng vai trò lưu trữ và cấp phát các Memento. Nó có trách nghiệm lưu trữ các State ở dạng Memento và cấp phát các State cho các Object khi cần.
II. Cách thức hoạt động:
Memento Pattern sẽ cấu trúc các dữ liệu cần lưu của một Object thành một State, sau đó sẽ lưu lại State này. Các State sau khi được lưu lại sẽ được gọi là các Memento. CareTaker sẽ đóng vai trò lưu trữ các State thành Memento và xuất các Memento thành State để có thể sử dụng. Do trạng thái của các Object đều được lưu trữ trong State nên khi State này được truyền qua các Object khác nhau thì sẽ không để lộ các implement chi tiết của các Object đó.
III. Memento Pattern được sử dụng khi nào?
Memento Pattern được sử dụng bất cứ khi nào chúng ta muốn lưu và sau đó khôi phục trạng thái của một Object. Ví dụ như khi chúng ta chơi game, chúng ta muốn lưu lại tất cả những trạng thái chúng ta đã chơi trước đó để sau khi quit game và mở lại thì chúng ta có thể tiếp tục chơi, khi đó ta có thể áp dụng Memento Pattern để giải quyết bài toán này.
IV. Ví dụ:
Trong ví dụ, chúng ta sẽ sử dụng class Game
với vai trò của Originator
. Bên trong class Game
sẽ chứa một State
để lưu lại các trạng thái của nó. Trạng thái của Game
sẽ được lưu vào trong UserDefault
, vì vậy chúng ta cần adopt Codable
protocol.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <span class="token comment">// MARK: - Originator</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Game</span><span class="token punctuation">:</span> <span class="token builtin">Codable</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">State</span><span class="token punctuation">:</span> <span class="token builtin">Codable</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">var</span> attemptsRemaining<span class="token punctuation">:</span> <span class="token builtin">Int</span> <span class="token operator">=</span> <span class="token number">3</span> <span class="token keyword">public</span> <span class="token keyword">var</span> level<span class="token punctuation">:</span> <span class="token builtin">Int</span> <span class="token operator">=</span> <span class="token number">1</span> <span class="token keyword">public</span> <span class="token keyword">var</span> score<span class="token punctuation">:</span> <span class="token builtin">Int</span> <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">var</span> state <span class="token operator">=</span> <span class="token function">State</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">public</span> <span class="token keyword">func</span> <span class="token function">rackUpMassivePoints</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> state<span class="token punctuation">.</span>score <span class="token operator">+</span><span class="token operator">=</span> <span class="token number">9002</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">func</span> <span class="token function">monstersEatPlayer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> state<span class="token punctuation">.</span>attemptsRemaining <span class="token operator">-</span><span class="token operator">=</span> <span class="token number">1</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Object Game
sẽ được lưu vào UserDefault
ở dạng Data
. Do vậy, Data
trong Swift ở ví dụ này sẽ đóng vai trò là Memento.
1 2 3 | <span class="token comment">// MARK: - Memento</span> <span class="token keyword">typealias</span> <span class="token builtin">GameMemento</span> <span class="token operator">=</span> <span class="token builtin">Data</span> |
Để quản lý việc lưu trữ các Memento, chúng ta sẽ cần tới CareTaker. GameSystem
đóng vai trò là CareTaker. Class này sẽ có trách nghiệm lưu trữ hoặc lấy các Memento khi cần thiết.
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 | <span class="token comment">// MARK: - CareTaker</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">GameSystem</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">let</span> decoder <span class="token operator">=</span> <span class="token function">JSONDecoder</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">private</span> <span class="token keyword">let</span> encoder <span class="token operator">=</span> <span class="token function">JSONEncoder</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">private</span> <span class="token keyword">let</span> userDefaults <span class="token operator">=</span> <span class="token builtin">UserDefaults</span><span class="token punctuation">.</span>standard <span class="token keyword">public</span> <span class="token keyword">func</span> <span class="token function">save</span><span class="token punctuation">(</span><span class="token number">_</span> game<span class="token punctuation">:</span> <span class="token builtin">Game</span><span class="token punctuation">,</span> title<span class="token punctuation">:</span> <span class="token builtin">String</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> data <span class="token operator">=</span> <span class="token keyword">try</span> encoder<span class="token punctuation">.</span><span class="token function">encode</span><span class="token punctuation">(</span>game<span class="token punctuation">)</span> userDefaults<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> forKey<span class="token punctuation">:</span> title<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">func</span> <span class="token function">load</span><span class="token punctuation">(</span>title<span class="token punctuation">:</span> <span class="token builtin">String</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">Game</span> <span class="token punctuation">{</span> <span class="token keyword">guard</span> <span class="token keyword">let</span> data <span class="token operator">=</span> userDefaults<span class="token punctuation">.</span><span class="token function">data</span><span class="token punctuation">(</span>forKey<span class="token punctuation">:</span> title<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">let</span> game <span class="token operator">=</span> <span class="token keyword">try</span><span class="token operator">?</span> decoder<span class="token punctuation">.</span><span class="token function">decode</span><span class="token punctuation">(</span><span class="token builtin">Game</span><span class="token punctuation">.</span><span class="token keyword">self</span><span class="token punctuation">,</span> from<span class="token punctuation">:</span> data<span class="token punctuation">)</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token builtin">Error</span><span class="token punctuation">.</span>gameNotFound <span class="token punctuation">}</span> <span class="token keyword">return</span> game <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">enum</span> <span class="token builtin">Error</span><span class="token punctuation">:</span> <span class="token builtin">String</span><span class="token punctuation">,</span> <span class="token builtin">Swift</span><span class="token punctuation">.</span><span class="token builtin">Error</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> gameNotFound <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Sử dụng:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <span class="token keyword">var</span> game <span class="token operator">=</span> <span class="token function">Game</span><span class="token punctuation">(</span><span class="token punctuation">)</span> game<span class="token punctuation">.</span><span class="token function">monstersEatPlayer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> game<span class="token punctuation">.</span><span class="token function">rackUpMassivePoints</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// Save Game</span> <span class="token keyword">let</span> gameSystem <span class="token operator">=</span> <span class="token function">GameSystem</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">try</span> gameSystem<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span>game<span class="token punctuation">,</span> title<span class="token punctuation">:</span> <span class="token string">"Best Game Ever"</span><span class="token punctuation">)</span> <span class="token comment">// New Game</span> game <span class="token operator">=</span> <span class="token function">Game</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"New Game Score: <span class="token interpolation"><span class="token delimiter variable">(</span>game<span class="token punctuation">.</span>state<span class="token punctuation">.</span>score<span class="token delimiter variable">)</span></span>"</span><span class="token punctuation">)</span> <span class="token comment">// Load Game</span> game <span class="token operator">=</span> <span class="token keyword">try</span><span class="token operator">?</span> gameSystem<span class="token punctuation">.</span><span class="token function">load</span><span class="token punctuation">(</span>title<span class="token punctuation">:</span> <span class="token string">"Best Game Ever"</span><span class="token punctuation">)</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"Loaded Game Score: <span class="token interpolation"><span class="token delimiter variable">(</span>game<span class="token operator">?</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>score<span class="token delimiter variable">)</span></span>"</span><span class="token punctuation">)</span> |
V. Tài liệu tham khảo:
- Design Pattern by Tutorials – Raywenderlich
- State Pattern by refactoring.guru