الگوی Memento یکی از الگوهای رفتاری می باشد. این الگو برای ذخیره
وضعیت اشیا و بازگشت به وضعیت قبلی یا بعدی به کار برده می شود. بنابراین می توان
برای پیاده سازی Undo و Redo از این الگو استفاده نمود. برای مثال در بازی های
کامپیوتری گاهی ممکن است تا مرحله ای پیش رفته و بخواهیم که وضعیت موجود تا آن
مرحله را ذخیره کنیم تا در مراجعه بعدی نیازی به شروع از ابتدا نداشته باشیم، در
برخی محاسبات و تراکنش های سنگین نیز به دلیل زمان بر و هزینه زیاد ممکن است نیاز
به ذخیره تا مرحله خاصی وجود داشته باشد. در این موارد می توان از الگوی Memento
استفاده نمود. ذخیره اطلاعات توسط این الگو بسته به نیاز می تواند در حافظه اصلی و
یا حافظه های جانبی مثل فایل و... انجام شود و به همین دلیل می تواند هزینه بر
باشد. نام دیگر این الگو Token می باشد و به دلیل ذخیره و نگهداری وضعیت ها
به Memento به معنی یادداشت یا یادگاری نامگذاری شده است.
کاربردهای الگوی طراحی Memento
از جمله کاربردهای الگوی Memento می توان به موارد زیر اشاره نمود:
تفاوت الگوی Command و Memento
اگر به خاطر داشته باشید در الگوی Command نیز اشاره شد که این الگو
برای پیاده سازی عملیات Undo و Redo می تواند استفاده شود. اما تفاوت این دو الگو
در چیست؟
- در الگوی Command عملیات مورد نظر و عکس آن ها توسط توابع پیاده سازی می
شوند و با اجرای آن ها می توان به وضعیت خاصی رسید و امکان نگهداری وضعیت وجود
ندارد اما در الگوی Memento خود وضعیت و اطلاعات آن را می توان نگهداری نمود.
بنابراین در مواردی که نیاز به نگهداری اطلاعات وضعیت وجود دارد از الگوی
Memento استفاده می شود.
- طبق روالی که در بالا به آن اشاره شد الگوی Command به حافظه نیازی ندارد و
با اجرای توابع به وضعیت می رسیم اما الگوی Memento نیازمند حافظه ای برای
ذخیره اطلاعات وضعیت می باشد.
- در واقع الگوی Command زمینه را برای پیاده سازی عملیات فراهم می کند اما
اطلاعاتی که در هر تغییر باید بازگردانی شود توسط الگوی Memento ذخیره می شود.
نکته کلیدی
نکته کلیدی که در این الگو وجود دارد این است که ذخیره وضعیت یک شی
کاملا متفاوت از شی می باشد و استفاده کننده از این الگو به هیچ عنوان به
یادداشت ها دسترسی ندارد بلکه دسترسی به یادداشت ها فقط برای ایجاد کننده یادداشت
ها امکان پذیر است.
نمودار کلاس دیاگرام الگوی طراحی Memento
همان طور که در دیاگرام مشخص است نمودار کلاس الگوی Memento شامل کلاس های زیر است:
- کلاس Originator یا ایجاد کننده: در این
کلاس اشیا می توانند وضعیت خود را ذخیره نمایند و در واقع توابع مورد استفاده
در این کلاس پیاده سازی می شوند. این کلاس وضعیت داخلی شی Originator را ذخیره
می کند. Memento ممکن است وضعیت داخلی Originator را در صورت لزوم در
Originator به همان اندازه یا کمتر ذخیره کند و از دسترسی سایر اشیا به جز
Originator به آن جلوگیری می کند.
- کلاس Memento یا یادداشت: نگهداری وضعیت
را به عهده دارد و به سایر کلاس ها اجازه دسترسی را نمی دهد. این کلاس یک
Memento شامل یک Snapshot از وضعیت کنونی داخلی ایجاد می کند و به Memento
اجازه برگرداندن وضعیت داخلی را می دهد.
- کلاس Caretaker یا سرپرست: لیستی از
وضعیت ها را نگهداری می کند و هرگز عملیاتی را بر روی یادداشت ها انجام نمی
دهد. این کلاس وظیفه نگهداری امن Memento را به عهده دارد و هرگز محتوای
Memento را بررسی ننموده و عملیاتی را بر روی آن انجام نمی دهد .
همچنین این الگو دارای دو واسط می باشد:
- واسط Originator: یک واسط گسترده به سمت
ایجاد کننده وجود دارد که به این کلاس اجازه می دهد به هر چیزی که می خواهد
ذخیره کند دسترسی داشته باشد.
- واسط Caretaker: یک واسط محدود شده به
سمت سرپرست وجود دارد که فقط می تواند به ارجاع های کلاس یادداشت دسترسی داشته
باشد.
در ادامه، پیاده سازی این الگو را در یک مثال توضیح می دهیم:
فرض کنید در یک صفحه وب یک فیلد متنی دارید که می خواهید عملیات ذخیره
و Undo و Redo را برای متن تایپ شده در این فیلد پیاده سازی نمایید، به صورتی که با
کلیک بر روی کلید Undo متن تایپ شده رد فیلد به مقدار قبلی که ذخیره شده بود
برگردانده شود و برعکس با کلیک بر روی کلید Redo به متن بعدی برگردانده شود. با
استفاده از الگوی Memento پیاده سازی به صورت زیر خواهد بود:
کلاس ایجاد کننده که در آن وضعیت و عملیات مورد استفاده به صورت زیر
تعریف می شود:
public class Originator
{
private string article;
public void set(string newArticle)
{
var message = String.Format("From Originator: Current Version of Article {0}",newArticle);
article = newArticle;
}
public Memento storeInMemento()
{
var message = String.Format("From Originator: Saving to Memento");
return new Memento(article);
}
public string restoreFromMemento(Memento memento)
{
article = memento.getSavedArticle();
var message = String.Format("From Originator: Previous Article Saved in Memento");
return article;
}
}
کلاس یادداشت ها که وضعیت را نگهداری نموده و آن را بازیابی می نماید
و به صورت زیر پیاده سازی می شود:
public class Memento
{
private string article;
public Memento(string articleSaved)
{
article = articleSaved;
}
public string getSavedArticle()
{
return article;
}
}
کلاس سرپرست که لیستی از وضعیت ها را نگهداری می نماید و به این ترتیب
وضعیت در مراحل مختلف قابل دسترسی خواهد بود:
public class Caretaker
{
List savedArticles = new List();
public void addMemento(Memento m)
{
savedArticles.Add(m);
}
public Memento getMemento(int index)
{
return savedArticles[index];
}
}
در صفحه مورد نظر نیز باید متغییرهایی از کلاس های فوق به صورت زیر ایجاد نمود:
static Caretaker caretaker = new Caretaker();
static Originator originator = new Originator();
static int saveFiles = 0, currentArticlce = 0;
سپس رخدادهای مربوط به هر کدام از کلیدهای موجود در صفحه را به صورت زیر پیاده سازی می نماییم:
protected void btnSaveListener_Click(object sender, EventArgs e)
{
originator.set(txtArticle.Text);
caretaker.addMemento(originator.storeInMemento());
saveFiles++;
currentArticlce++;
}
protected void btnUndoListener_Click(object sender, EventArgs e)
{
if (currentArticlce >= 1)
{
currentArticlce--;
string txtString = originator.restoreFromMemento(caretaker.getMemento(currentArticlce));
txtArticle.Text = txtString;
btnRedoListener.Enabled = true;
}
else
{
btnUndoListener.Enabled = false;
}
}
protected void btnRedoListener_Click(object sender, EventArgs e)
{
if(saveFiles-1>currentArticlce)
{
currentArticlce++;
string txtString = originator.restoreFromMemento(caretaker.getMemento(currentArticlce));
txtArticle.Text = txtString;
btnUndoListener.Enabled = true;
}
else
{
btnRedoListener.Enabled = false;
}
}