Перейти до вмісту

Memento

Memento — поведінковий патерн, що дає можливість зберегти і відновити стан обʼєкта без того, щоб зовнішній код лазив у його приватні поля. Три ролі: Originator (обʼєкт зі станом), Memento (знімок), Caretaker (хранитель, що тримає колекцію знімків — зазвичай стек).

Фотоальбом: ти робиш фото себе сьогодні і кладеш в альбом. За рік відкриваєш альбом і «відновлюєш» вигляд — але сам ти не змінюєшся під альбомом, це альбом знає твій минулий стан. Альбом не розуміє, що на фото — він просто зберігає.

  • Треба undo/redo — стек знімків.
  • Треба snapshot стану для rollback після виняткової ситуації.
  • Стан обʼєкта складний, а його відновлення з первинних джерел (БД) — дороге або неможливе.
  • Треба серіалізувати стан (наприклад, для збереження у Shared Memory або відправки по RFC).

Ідея: memento — окремий клас або структура, доступна тільки Originator-у. Caretaker тримає memento, але не може читати вміст — тільки передати назад в Originator.

" Memento — PRIVATE-інстанс. GLOBAL FRIENDS дає доступ лише Originator-у.
CLASS zcl_editor_memento DEFINITION PUBLIC FINAL CREATE PRIVATE
GLOBAL FRIENDS zcl_editor.
PUBLIC SECTION.
METHODS constructor
IMPORTING is_state TYPE zty_editor_state.
PRIVATE SECTION.
DATA ms_state TYPE zty_editor_state.
ENDCLASS.
CLASS zcl_editor_memento IMPLEMENTATION.
METHOD constructor. ms_state = is_state. ENDMETHOD.
ENDCLASS.
" Originator
CLASS zcl_editor DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS:
type IMPORTING iv_text TYPE string,
cursor IMPORTING iv_pos TYPE i,
save RETURNING VALUE(ro) TYPE REF TO zcl_editor_memento,
restore IMPORTING io_memento TYPE REF TO zcl_editor_memento.
PRIVATE SECTION.
DATA ms_state TYPE zty_editor_state.
ENDCLASS.
CLASS zcl_editor IMPLEMENTATION.
METHOD type. ms_state-text = ms_state-text && iv_text. ENDMETHOD.
METHOD cursor. ms_state-cursor_pos = iv_pos. ENDMETHOD.
METHOD save.
ro = NEW zcl_editor_memento( is_state = ms_state ).
ENDMETHOD.
METHOD restore.
" GLOBAL FRIENDS дозволяє читати приватне поле
ms_state = io_memento->ms_state.
ENDMETHOD.
ENDCLASS.
" Caretaker — тримає стек. Не читає і не створює memento сам.
CLASS zcl_history DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS:
push IMPORTING io_memento TYPE REF TO zcl_editor_memento,
pop RETURNING VALUE(ro) TYPE REF TO zcl_editor_memento.
PRIVATE SECTION.
DATA mt_stack TYPE STANDARD TABLE OF REF TO zcl_editor_memento WITH EMPTY KEY.
ENDCLASS.

Використання:

DATA(lo_editor) = NEW zcl_editor( ).
DATA(lo_history) = NEW zcl_history( ).
lo_editor->type( 'Hello ' ).
lo_history->push( lo_editor->save( ) ). " snapshot до другої дії
lo_editor->type( 'world' ).
" Ой, не те ввів
lo_editor->restore( lo_history->pop( ) ). " повертаємось

Caretaker (zcl_history) тримає memento-обʼєкти, але не знає їхнього вмісту — ms_state приватне, доступне тільки через GLOBAL FRIENDS zcl_editor. Інкапсуляція не порушена.

Memento через копію структури (простий варіант)

Section titled “Memento через копію структури (простий варіант)”

Якщо стан — структура-значення, і нема потреби ховати її тип від caretaker-а:

METHODS:
save RETURNING VALUE(rs_state) TYPE zty_editor_state,
restore IMPORTING is_state TYPE zty_editor_state.

Швидше і простіше, але caretaker бачить поля стану. Для внутрішнього use — ок.

  • EXPORT TO MEMORY / IMPORT FROM MEMORY — memento на рівні ABAP runtime, не обʼєктний, але концептуально — снапшот.
  • Shared Memory Objects — memento, що переживає між сесіями.
  • RAP Draft — memento для збереження чернетки редагованої сутності.
  • Transport Organizer — snapshot змін обʼєктів у пакеті.
  • Shallow vs deep copy — якщо стан містить REF-поля, memento має клонувати їх глибоко, інакше зміна в Originator поламає minented стан. Див. Prototype.
  • Навʼязлива дружба. GLOBAL FRIENDS — крутий інструмент, але створює жорсткий звʼязок двох класів. Перш ніж використовувати — подумай, чи справді memento недоступне ззовні є вимогою.
  • Stale memento. Memento зроблено, стан обʼєкта змінився, memento вже не відображає актуальний стан — передбачуване, але несподіване для junior-ів. Документуй це в коментарі.
  • Undo за межами обʼєкта. Memento відновлює стан обʼєкта. Але якщо обʼєкт вже зробив side-effect (INSERT у БД, email) — undo memento не відкотить його. Див. Command.

Коли НЕ використовувати

Section titled “Коли НЕ використовувати”
  • Стан тривіальний (одне-два поля) — просто збережи змінні.
  • Snapshot непотрібен, тільки перевірка «якщо впало — rollback». Використовуй БД-транзакцію/SAP LUW.
  • Обʼєкт і так serialize-able через CALL TRANSFORMATION id — серіалізуй у XML.