التذكار.
التذكار هو نمط تصميم سلوكي يتيح لك حفظ الحالة السابقة لكائن واستعادتها دون الكشف عن تفاصيل تنفيذه.
##Intent
التذكار هو نمط تصميم سلوكي يتيح لك حفظ الحالة السابقة لكائن واستعادتها دون الكشف عن تفاصيل تنفيذه.

##Problem
تخيّل أنك تبني تطبيق محرّر نصوص. وبالإضافة إلى تحرير النصوص البسيط، يستطيع محرّرك تنسيق النص وإدراج صور ضمن النص، وما إلى ذلك.
في مرحلة ما، قرّرت أن تتيح للمستخدمين التراجع عن أي عمليات أُجريت على النص. وقد أصبحت هذه الميزة شائعة جدًا على مرّ السنين حتى بات الناس يتوقّعون وجودها في كل تطبيق. ولأجل التنفيذ، اخترت اتّباع النهج المباشر. فقبل تنفيذ أي عملية، يسجّل التطبيق حالة جميع الكائنات ويحفظها في مكان تخزين ما. ولاحقًا، عندما يقرّر المستخدم التراجع عن إجراء، يجلب التطبيق أحدث لقطة من السجلّ ويستخدمها لاستعادة حالة جميع الكائنات.

قبل تنفيذ عملية ما، يحفظ التطبيق لقطة من حالة الكائنات، يمكن استخدامها لاحقًا لإعادة الكائنات إلى حالتها السابقة.
لنفكّر في لقطات الحالة هذه. كيف ستُنشئ واحدة منها بالضبط؟ من المرجّح أنك ستحتاج إلى المرور على جميع حقول الكائن ونسخ قيمها إلى مكان التخزين. غير أن هذا لن ينجح إلا إذا كانت قيود الوصول إلى محتويات الكائن متساهلة إلى حدّ كبير. ولسوء الحظ، فإن معظم الكائنات الحقيقية لا تسمح للآخرين بالاطّلاع على داخلها بهذه السهولة، إذ تُخفي كل البيانات المهمّة في حقول خاصة (private).
لنتجاهل تلك المشكلة الآن ولنفترض أن كائناتنا تتصرّف كالهيبيز: تفضّل العلاقات المفتوحة وتُبقي حالتها عامة (public). ومع أن هذا النهج سيحلّ المشكلة الآنيّة ويتيح لك إنتاج لقطات من حالات الكائنات وقتما تشاء، إلا أنه لا يزال يعاني من بعض المشكلات الجدّية. ففي المستقبل قد تقرّر إعادة هيكلة بعض فئات المحرّر، أو إضافة بعض الحقول أو إزالتها. يبدو الأمر سهلًا، لكنه سيتطلّب أيضًا تعديل الفئات المسؤولة عن نسخ حالة الكائنات المتأثّرة.

كيف تُنشئ نسخة من الحالة الخاصة للكائن؟
لكن الأمر لا يتوقف عند هذا الحدّ. لنتأمّل «اللقطات» الفعلية لحالة المحرّر. ما البيانات التي تحتويها؟ كحدّ أدنى مطلق، يجب أن تحتوي على النص الفعلي، وإحداثيات المؤشّر، وموضع التمرير الحالي، وما إلى ذلك. ولإنشاء لقطة، ستحتاج إلى جمع هذه القيم ووضعها في نوع ما من الحاويات.
من المرجّح أنك ستخزّن الكثير من كائنات الحاويات هذه داخل قائمة ما تمثّل السجلّ. ولذلك ستكون الحاويات على الأرجح كائنات من فئة واحدة. ولن تمتلك هذه الفئة دوالّ تقريبًا، بل عددًا كبيرًا من الحقول التي تعكس حالة المحرّر. ولكي تتيح للكائنات الأخرى كتابة البيانات إلى اللقطة وقراءتها منها، ستحتاج على الأرجح إلى جعل حقولها عامة (public). وهذا سيكشف جميع حالات المحرّر، الخاصة منها وغير الخاصة. وستصبح الفئات الأخرى معتمِدة على كل تغيير صغير في فئة اللقطة، وهي تغييرات كانت لتحدث ضمن حقول ودوالّ خاصة دون التأثير على الفئات الخارجية.
يبدو أننا وصلنا إلى طريق مسدود: فإمّا أن تكشف كل التفاصيل الداخلية للفئات، مما يجعلها هشّة للغاية، وإمّا أن تقيّد الوصول إلى حالتها، مما يجعل إنتاج اللقطات مستحيلًا. فهل من طريقة أخرى لتنفيذ ميزة «التراجع»؟
##Solution
جميع المشكلات التي واجهناها للتوّ سببها تغليف (encapsulation) معطوب. فبعض الكائنات تحاول القيام بأكثر مما يُفترض بها. ولجمع البيانات اللازمة لتنفيذ إجراء ما، تقتحم المساحة الخاصة لكائنات أخرى بدلًا من ترك هذه الكائنات تنفّذ الإجراء الفعلي بنفسها.
يفوّض نمط التذكار مهمة إنشاء لقطات الحالة إلى المالك الفعلي لتلك الحالة، وهو كائن المُنشئ (originator). وبذلك، بدلًا من أن تحاول كائنات أخرى نسخ حالة المحرّر من «الخارج»، تستطيع فئة المحرّر نفسها إنشاء اللقطة لأنها تملك وصولًا كاملًا إلى حالتها الخاصة.
يقترح النمط تخزين نسخة حالة الكائن في كائن خاص يُسمّى التذكار (memento). ومحتويات التذكار غير متاحة لأي كائن آخر سوى الكائن الذي أنتجه. ويجب على الكائنات الأخرى التواصل مع التذكارات عبر واجهة محدودة قد تتيح جلب البيانات الوصفية للقطة (وقت الإنشاء، واسم العملية المُنفَّذة، وما إلى ذلك)، لكن دون الوصول إلى حالة الكائن الأصلي المحفوظة في اللقطة.

يملك المُنشئ وصولًا كاملًا إلى التذكار، بينما لا يستطيع الوصيّ الوصول إلا إلى البيانات الوصفية.
تتيح لك هذه السياسة المقيِّدة تخزين التذكارات داخل كائنات أخرى تُسمّى عادةً الأوصياء (caretakers). وبما أن الوصيّ يتعامل مع التذكار عبر الواجهة المحدودة فقط، فإنه غير قادر على العبث بالحالة المخزّنة داخل التذكار. وفي الوقت نفسه، يملك المُنشئ وصولًا إلى جميع الحقول داخل التذكار، مما يتيح له استعادة حالته السابقة وقتما يشاء.
في مثال محرّر النصوص لدينا، يمكننا إنشاء فئة سجلّ منفصلة لتؤدّي دور الوصيّ. وستنمو مكدّسة (stack) من التذكارات مخزّنة داخل الوصيّ في كل مرة يوشك فيها المحرّر على تنفيذ عملية. ويمكنك حتى عرض هذه المكدّسة ضمن واجهة استخدام التطبيق، لتُظهر للمستخدم سجلّ العمليات المُنفَّذة سابقًا.
وعندما يطلب المستخدم التراجع، يأخذ السجلّ أحدث تذكار من المكدّسة ويعيده إلى المحرّر طالبًا التراجع. وبما أن المحرّر يملك وصولًا كاملًا إلى التذكار، فإنه يغيّر حالته الخاصة بالقيم المأخوذة من التذكار.
##Structure
تنفيذ قائم على الفئات المتداخلة
يعتمد التنفيذ الكلاسيكي للنمط على دعم الفئات المتداخلة (nested classes)، المتوفّر في كثير من لغات البرمجة الشائعة (مثل C++ وC# وJava).


-
تستطيع فئة المُنشئ (Originator) إنتاج لقطات من حالتها الخاصة، إضافةً إلى استعادة حالتها من اللقطات عند الحاجة.
-
التذكار (Memento) هو كائن قيمة (value object) يعمل بمثابة لقطة من حالة المُنشئ. ومن الممارسات الشائعة جعل التذكار غير قابل للتغيير (immutable) وتمرير البيانات إليه مرة واحدة فقط عبر الباني (constructor).
-
يعرف الوصيّ (Caretaker) ليس فقط «متى» و«لماذا» يلتقط حالة المُنشئ، بل أيضًا متى ينبغي استعادة الحالة.
يمكن للوصيّ تتبّع تاريخ المُنشئ عبر تخزين مكدّسة من التذكارات. وعندما يحتاج المُنشئ إلى العودة إلى الوراء في التاريخ، يجلب الوصيّ التذكار الأعلى من المكدّسة ويمرّره إلى دالة الاستعادة في المُنشئ.
-
في هذا التنفيذ، تكون فئة التذكار متداخلة داخل المُنشئ. وهذا يتيح للمُنشئ الوصول إلى حقول التذكار ودوالّه، رغم أنها معلَنة على أنها خاصة (private). ومن جهة أخرى، يملك الوصيّ وصولًا محدودًا جدًا إلى حقول التذكار ودوالّه، مما يتيح له تخزين التذكارات في مكدّسة دون العبث بحالتها.
تنفيذ قائم على واجهة وسيطة
ثمة تنفيذ بديل يناسب لغات البرمجة التي لا تدعم الفئات المتداخلة (نعم يا PHP، أعنيكِ أنتِ).
-
في غياب الفئات المتداخلة، يمكنك تقييد الوصول إلى حقول التذكار عبر إرساء اتّفاق مفاده أن الأوصياء لا يمكنهم التعامل مع التذكار إلا من خلال واجهة وسيطة معلَنة صراحةً، لا تعلن إلا الدوالّ المتعلّقة بالبيانات الوصفية للتذكار.
-
ومن جهة أخرى، يستطيع المُنشئون التعامل مع كائن التذكار مباشرةً، مع الوصول إلى الحقول والدوالّ المعلَنة في فئة التذكار. وعيب هذا النهج هو أنك تحتاج إلى إعلان جميع أعضاء التذكار على أنها عامة (public).
تنفيذ بتغليف أكثر صرامة
ثمة تنفيذ آخر يكون مفيدًا عندما لا تريد ترك أدنى فرصة للفئات الأخرى للوصول إلى حالة المُنشئ عبر التذكار.
-
يتيح هذا التنفيذ وجود أنواع متعدّدة من المُنشئين والتذكارات. ويعمل كل مُنشئ مع فئة تذكار مقابِلة له. ولا يكشف المُنشئون ولا التذكارات حالتهم لأي أحد.
-
يُمنَع الأوصياء الآن صراحةً من تغيير الحالة المخزّنة في التذكارات. وعلاوةً على ذلك، تصبح فئة الوصيّ مستقلّة عن المُنشئ لأن دالة الاستعادة باتت معرَّفة الآن في فئة التذكار.
-
يصبح كل تذكار مرتبطًا بالمُنشئ الذي أنتجه. ويمرّر المُنشئ نفسه إلى باني التذكار، مع قيم حالته. وبفضل العلاقة الوثيقة بين هاتين الفئتين، يستطيع التذكار استعادة حالة مُنشئه، شريطة أن يكون الأخير قد عرّف دوالّ التعيين (setters) المناسبة.
##Pseudocode
يستخدم هذا المثال نمط التذكار جنبًا إلى جنب مع نمط الأمر لتخزين لقطات من حالة محرّر النصوص المعقّد واستعادة حالة سابقة من هذه اللقطات عند الحاجة.

حفظ لقطات من حالة محرّر النصوص.
تعمل كائنات الأوامر بوصفها أوصياء. فهي تجلب تذكار المحرّر قبل تنفيذ العمليات المرتبطة بالأوامر. وعندما يحاول المستخدم التراجع عن أحدث أمر، يستطيع المحرّر استخدام التذكار المخزّن في ذلك الأمر لإعادة نفسه إلى الحالة السابقة.
لا تعلن فئة التذكار أي حقول أو دوالّ جلب أو تعيين عامة. ولذلك لا يستطيع أي كائن تعديل محتوياتها. والتذكارات مرتبطة بكائن المحرّر الذي أنشأها. وهذا يتيح للتذكار استعادة حالة المحرّر المرتبط به عبر تمرير البيانات من خلال دوالّ التعيين في كائن المحرّر. وبما أن التذكارات مرتبطة بكائنات محرّر محدّدة، يمكنك جعل تطبيقك يدعم عدّة نوافذ محرّر مستقلّة مع مكدّسة تراجع مركزية.
// المُنشئ يحتفظ ببعض البيانات المهمّة التي قد تتغيّر بمرور
// الوقت. كما يعرّف دالةً لحفظ حالته داخل تذكار ودالةً أخرى
// لاستعادة الحالة منه.
class Editor is
private field text, curX, curY, selectionWidth
method setText(text) is
this.text = text
method setCursor(x, y) is
this.curX = x
this.curY = y
method setSelectionWidth(width) is
this.selectionWidth = width
// يحفظ الحالة الحالية داخل تذكار.
method createSnapshot():Snapshot is
// التذكار كائن غير قابل للتغيير؛ ولهذا يمرّر المُنشئ
// حالته إلى مُعامِلات باني التذكار.
return new Snapshot(this, text, curX, curY, selectionWidth)
// فئة التذكار تخزّن الحالة السابقة للمحرّر.
class Snapshot is
private field editor: Editor
private field text, curX, curY, selectionWidth
constructor Snapshot(editor, text, curX, curY, selectionWidth) is
this.editor = editor
this.text = text
this.curX = x
this.curY = y
this.selectionWidth = selectionWidth
// في مرحلة ما، يمكن استعادة حالة سابقة للمحرّر باستخدام
// كائن تذكار.
method restore() is
editor.setText(text)
editor.setCursor(curX, curY)
editor.setSelectionWidth(selectionWidth)
// يمكن لكائن الأمر أن يعمل بوصفه وصيًّا. وفي تلك الحالة،
// يحصل الأمر على تذكار قبيل تغييره لحالة المُنشئ مباشرةً.
// وعند طلب التراجع، يستعيد حالة المُنشئ من تذكار.
class Command is
private field backup: Snapshot
method makeBackup() is
backup = editor.createSnapshot()
method undo() is
if (backup != null)
backup.restore()
// ...
##Applicability
استخدم نمط التذكار عندما تريد إنتاج لقطات (snapshots) من حالة الكائن لتتمكّن من استعادة حالة سابقة له.
يتيح لك نمط التذكار إنشاء نسخ كاملة من حالة الكائن، بما في ذلك الحقول الخاصة (private)، وتخزينها بمعزل عن الكائن. ومع أنّ معظم الناس يتذكّرون هذا النمط بفضل حالة استخدام «التراجع» (undo)، فإنه لا غنى عنه أيضًا عند التعامل مع المعاملات (transactions) (أي عندما تحتاج إلى التراجع عن عملية عند حدوث خطأ).
استخدم النمط عندما يكون الوصول المباشر إلى حقول الكائن أو دوال الجلب/التعيين (getters/setters) مُخِلًّا بتغليفه (encapsulation).
يجعل التذكار الكائنَ نفسه مسؤولًا عن إنشاء لقطة من حالته. ولا يمكن لأيّ كائن آخر قراءة اللقطة، مما يجعل بيانات حالة الكائن الأصلي آمنة ومحمية.
##How to Implement
-
حدِّد الفئة التي ستؤدّي دور المُنشئ. من المهم معرفة ما إذا كان البرنامج يستخدم كائنًا مركزيًا واحدًا من هذا النوع أم عدّة كائنات أصغر.
-
أنشئ فئة التذكار. وأعلِن، واحدًا تلو الآخر، مجموعة من الحقول تعكس الحقول المعلَنة داخل فئة المُنشئ.
-
اجعل فئة التذكار غير قابلة للتغيير (immutable). فينبغي للتذكار أن يقبل البيانات مرة واحدة فقط عبر الباني. ولا ينبغي أن تمتلك الفئة أي دوالّ تعيين (setters).
-
إذا كانت لغة برمجتك تدعم الفئات المتداخلة، فضع التذكار متداخلًا داخل المُنشئ. وإن لم تكن كذلك، فاستخرج واجهة فارغة من فئة التذكار واجعل جميع الكائنات الأخرى تستخدمها للإشارة إلى التذكار. ويمكنك إضافة بعض عمليات البيانات الوصفية إلى الواجهة، لكن دون أي شيء يكشف حالة المُنشئ.
-
أضِف إلى فئة المُنشئ دالةً لإنتاج التذكارات. وينبغي للمُنشئ أن يمرّر حالته إلى التذكار عبر وسيط واحد أو أكثر من وسائط باني التذكار.
وينبغي أن يكون نوع القيمة المُعادة من الدالة هو الواجهة التي استخرجتها في الخطوة السابقة (بافتراض أنك استخرجتها أصلًا). وخلف الكواليس، ينبغي للدالة المنتِجة للتذكارات أن تتعامل مباشرةً مع فئة التذكار.
-
أضِف إلى فئة المُنشئ دالةً لاستعادة حالته. وينبغي أن تقبل كائن تذكار كوسيط. وإن كنت قد استخرجت واجهة في الخطوة السابقة، فاجعلها نوع المُعامِل. وفي هذه الحالة، ستحتاج إلى تحويل نوع (typecast) الكائن الوارد إلى فئة التذكار، لأن المُنشئ يحتاج إلى وصول كامل إلى ذلك الكائن.
-
ينبغي للوصيّ، سواء أكان يمثّل كائن أمر أم سجلًّا أم شيئًا مختلفًا تمامًا، أن يعرف متى يطلب تذكارات جديدة من المُنشئ، وكيف يخزّنها، ومتى يستعيد حالة المُنشئ بتذكار معيّن.
-
يمكن نقل الرابط بين الأوصياء والمُنشئين إلى داخل فئة التذكار. وفي هذه الحالة، يجب أن يكون كل تذكار متّصلًا بالمُنشئ الذي أنشأه. وستنتقل دالة الاستعادة أيضًا إلى فئة التذكار. غير أن هذا كلّه لن يكون منطقيًا إلا إذا كانت فئة التذكار متداخلة داخل المُنشئ أو كانت فئة المُنشئ توفّر دوالّ تعيين (setters) كافية لإعادة كتابة حالتها.
##Pros & Cons
- يمكنك إنتاج لقطات من حالة الكائن دون الإخلال بتغليفه.
- يمكنك تبسيط كود المُنشئ بترك مهمة الاحتفاظ بتاريخ حالة المُنشئ للوصيّ.
- قد يستهلك التطبيق قدرًا كبيرًا من ذاكرة الوصول العشوائي (RAM) إذا أنشأ العملاء التذكارات بكثرة.
- ينبغي على الأوصياء (caretakers) تتبّع دورة حياة المُنشئ (originator) ليتمكّنوا من إتلاف التذكارات التي لم تعد مستخدمة.
- لا تستطيع معظم لغات البرمجة الديناميكية، مثل PHP وPython وJavaScript، ضمان بقاء الحالة الموجودة داخل التذكار دون مساس.
##Relations with Other Patterns
-
يمكنك استخدام نمطَي الأمر والتذكار معًا عند تنفيذ ميزة «التراجع». وفي هذه الحالة، تكون الأوامر مسؤولة عن تنفيذ عمليات مختلفة على كائن هدف، بينما تحفظ التذكارات حالة ذلك الكائن قبيل تنفيذ الأمر مباشرةً.
-
يمكنك استخدام التذكار مع المُكرِّر لالتقاط حالة التكرار الحالية والتراجع عنها عند الضرورة.
-
أحيانًا يكون النموذج الأولي بديلًا أبسط من التذكار. وينجح ذلك إذا كان الكائن الذي تريد تخزين حالته في السجلّ بسيطًا إلى حدّ معقول ولا يملك روابط إلى موارد خارجية، أو كانت الروابط سهلة إعادة الإنشاء.