
سلام دوستان، امیدوارم که حالتون خوب باشه.
خیلی از شما عزیزان مطالب زیادی رو در خصوص الگوی های طراحی مطالعه کردید و حتی با خیلی از این الگوها آشنا هستید ولی درک این الگوها با مثال های عملی، خیلی خیلی راحت تر هست. برای همین هم تصمیم گرفتم هر از گاهی تعدادی مثال در مورد الگوهای طراحی قرار بدم.
الگوی طراحی Memento یکی از الگوهای رفتاری (behavioral) هست که برای ذخیره حالت های متفاوت یک کلاس و بازیابی اونها، ازش استفاده میکنیم. مثال های زیادی میشه برای این الگو مطرح کرد. مثل پیاده سازی قابلیت undo/redo و مدیریت تراکنش ها.
ساختار کلی Memento رو میتونید در شکل زیر ببینید:

سه کلاس موجود در این کلاس دیاگرام رو با هم بررسی میکنیم. البته در پروژه خودتون، اسامی این کلاس ها میتونن متفاوت باشن و الزامی نیست که از این اسامی استفاده بشه:
Originator در قالب کلاس Memento، در این کلاس ذخیره و بعدا در صورت نیاز از این کلاس خوانده میشن.مثال موجود در این پست، یک پیاده سازی بسیار ساده از الگوی طراحی Memento رو شامل میشه. یک کلاس داریم که اطلاعات حساب بانکی در اون نگهداری میشه و اسمش رو Account گذاشتم و این کلاس معادل Originator هست. این کلاس سه متد اصلی داره:
Memento برمیگردونه. مثل BEGIN در تراکنش های دیتابیسی میمونه و حکم آغاز تراکنش ها رو داره.start رو داره و اطلاعات نهایی کلاس رو در قالب Memento برمیگردونه.exception برخوردیم و میخوایم تراکنش به حالت ابتدایی خودش یعنی حالتی که در start داشت برگرده. این متد، یک نمونه از Memento رو دریافت و کلاس Account رو بر اساس اون تغییر میده.کلاس بعدی TransactionMemento هست. این کلاس معادل کلاس Memento و یک فیلد داره به اسم balance که دقیقا معادل همین فیلد در کلاس Account هست.
در نهایت، حالت های مختلف کلاس Account در قالب TransactionMemento در کلاس History ذخیره میشه. این کلاس معادل CareTaker در این الگو هست و یک لیست از TransactionMemento رو در خودش داره شامل دو متد اصلی push برای ذخیره یک memento در لیست و pop برای بازیابی آخرین memento از لیست است. در زیر، کلاس دیاگرام این پروژه رو با هم میبنیم:

قبل از اینکه شروع به پیاده سازی کنیم، این پروژه روی GitHub قرار گرفته و میتونید ساختارش رو بررسی و یا اجراش کنید.
فیلد ها و متد های کلاس Account:
balance: مقدار موجودی حساب کاربر رو نگهداری میکنه.deposit: یک عدد رو دریافت و در صورت مثبت بودن، اون رو به متغیر balance اضافه میکنه.withdraw: یک عدد رو دریافت و در صورت مثبت بودن، اون رو از متغیر balance کم میکنه.getBalance: مقدار فعلی حساب کاربر رو برمیگردونه.start: یک نسخه Memento از اطلاعات حساب (که فقط فیلد balance) هست رو ایجاد و برمیگردونه. end: دقیقا مثل متد start عمل میکنه.rollBack: یک Memento رو دریافت و در کلاس فعلی قرار میده. از اونجا که در کلاس فعلی فقط balance مد نظر ما هست، پس در این متد فقط balance مقدار دهی شده.همانطور که قبل تر گفته شد، این کلاس باید مقادیری از کلاس Originator که در اینجا Account هست رو برای ذخیره سازی در CareTaker اماده کنه. در اصل، CareTaker فقط میتونه Memento رو ذخیره کنه.
فیلد ها و متد های کلاس TransactionMemento:
balance: مقدار فعلی balance از کلاس Account در این متغیر قرار میگرد.settr/getter: این متد ها، ستر گترِ فیلد balance هستن.لیست حالت های مختلف از Memento در این کلاس قرار میگیره.
فیلد ها و متد های کلاس History:
transactionMemento: یک لیست از memento ها هست. وضعیت های مختلف کلاس Account در زمان اجرا، در این فیلد ذخیره خواهند شد.push: از اسمش مشخص هست. یک Memento دریافت میکنه و ذخیره اش میکنه. توی مثال مدیریت تراکنش، طبیعتا این Memento باید یک حالت پایدار از کلاس باشن.pop: این متد هم آخرین حالت کلاس(اخرین Memento ذخیره سازی شده) رو از لیست برمیداره و حذفش میکنه.(اگر نیاز به پیاده سازی قابلیت Redo در نرم افزار خودتون دارید، فقط index رو تغییر بدید و ایتم رو حذف نکنید.)خب، تا به اینجا ساختار الگوی Memento رو با هم پیاده کردیم. ولی حالا چطوری ازش استفاده کنیم؟ من یک فایل دارم که منطق برنامه داخلش پیاده سازی شده و میخوام تعداد زیادی مبلغ رو به حساب مشتری واریز کنم و طبیعتا این مقادیر باید در قالب تراکنش انجام بشن. یعنی فقط و فقط زمانی کار با موفقیت انجام میشه که تمامی اون مقادیر با موفقیت به حساب کاربر واریز یا برداشت بشن و مثلا اگر حین انجام دستورالعمل ها، خطایی ایجاد بشه، باید به حالت قبل از اولین دستورالعمل یعنی زمانی که تراکنش شروع شده برگردیم. کلاس اصلی برنامه من، RollbackManager.java هست.
همونطور که مشخص هست، یک ارایه به اسم instructions داریم که مبالغی که باید به حساب واریز یا برداشت بشن داخلش قرار داره. جلوتر چک میشه که اگر مبلغ مثبت بود، Deposit صدا زده بشه و اگر منفی بود Withdraw صدا زده بشه. ولی اگر مبلغ 0 باشه چی؟ در اینصورت یک اکسپشن برگردونده میشه.
قبل از شروع حلقه History.push(account.start()) استفاده شده. متد start یک Memento از حالت فعلی کلاس Account رو برمیگردونه و History اون رو ذخیره میکنه. پس در حال حاظر، وضعیت فعلی کلاس ذخیره شد.
حالا وارد تراکنش میشیم. بعد از اجرای تراکنش هم end صدا زده شده و خروجیش در history ذخیره شده. یعنی قبل و بعد از واریز و برداشت ها، تراکنش رو شروع و به پایان میرسونیم.
نکته حائز اهمیت در این بخش از کد هست.
catch (Exception e) { account.rollBack(History.pop()); System.out.println("transaction rollback: balance is " + account.getBalance()); }
در catch، از متد rollBack استفاده کردیم و مقداری که باید بهش داده بشه رو اخرین وضعیت history دریافت کردیم. یعنی در اصل وضعیتی که در ابتدای تراکنش ذخیره کرده بودیم رو.
کلاس رو اجرا و خروجی رو مشاهده میکنیم:
before transaction start: balance is 30.0 during transaction (item is: 20.0): balance is 50.0 during transaction (item is: 20.5): balance is 70.5 during transaction (item is: 13.5572): balance is 84.0572 during transaction (item is: 11.0): balance is 95.0572 during transaction (item is: 2993.0): balance is 3088.0572 during transaction (item is: -123.0): balance is 2965.0572 during transaction (item is: 8537.0): balance is 11502.0572 during transaction (item is: 884.0): balance is 12386.0572 during transaction (item is: 9281.0): balance is 21667.0572 during transaction (item is: 9987.0): balance is 31654.0572 during transaction (item is: 93847.0): balance is 125501.0572 during transaction (item is: -2.0): balance is 125499.0572 during transaction (item is: 9938.0): balance is 135437.05719999998 during transaction (item is: 9.0): balance is 135446.05719999998 during transaction (item is: 99.0): balance is 135545.05719999998 during transaction (item is: 918.0): balance is 136463.05719999998 during transaction (item is: 7374.0): balance is 143837.05719999998 during transaction (item is: 92492.0): balance is 236329.05719999998 during transaction (item is: 1.0): balance is 236330.05719999998 during transaction (item is: -20.0): balance is 236310.05719999998 during transaction (item is: 22.0): balance is 236332.05719999998 transaction rollback: balance is 30.0
همونطور که مشخص هست، در ابتدا 30 واحد به بالانس اضافه شد. بعد دستورالعمل ها تک تک اجرا شدن تا وقتی که به 22 رسیدیم. اگر آرایه instructions رو ببینید، بعد از 22، مقدار 0 هست. یعنی به اکسپشن برخوردیم پس rollBack انجام شده. آخرین خط خروجی هم مشخص هست که برگشتیم به ابتدای تراکنش.
از الگوی طراحی Memento برای ذخیره ی حالت های مختلفی از یک کلاس استفاده میکنیم. در این مقاله، به پیاده سازی یک مدیریت کننده تراکنش خیلی خیلی ساده پرداختیم و در اون از الگوی Memento استفاده کردیم.
امیدوارم براتون مفید باشه.