ویرگول
ورودثبت نام
شهاب اسکندری
شهاب اسکندری
شهاب اسکندری
شهاب اسکندری
خواندن ۷ دقیقه·۵ سال پیش

پیاده سازی یک مدیریت کننده تراکنش ساده با Memento Pattern در جاوا

مقدمه

سلام دوستان، امیدوارم که حالتون خوب باشه.

خیلی از شما عزیزان مطالب زیادی رو در خصوص الگوی های طراحی مطالعه کردید و حتی با خیلی از این الگوها آشنا هستید ولی درک این الگوها با مثال های عملی، خیلی خیلی راحت تر هست. برای همین هم تصمیم گرفتم هر از گاهی تعدادی مثال در مورد الگوهای طراحی قرار بدم.

الگوی طراحی Memento

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

ساختار کلی Memento رو میتونید در شکل زیر ببینید:

کلاس دیاگرام الگوی Memento
کلاس دیاگرام الگوی Memento

سه کلاس موجود در این کلاس دیاگرام رو با هم بررسی میکنیم. البته در پروژه خودتون، اسامی این کلاس ها میتونن متفاوت باشن و الزامی نیست که از این اسامی استفاده بشه:

  • کلاس Originator: این کلاس، کلاسی هست که ما میخوایم حالات مختلف اون رو ذخیره و بعدا در صورت نیاز بازیابی کنیم. معمولا دوتا متد توی این کلاس هست که از یکی برای ایجاد یک نسخه قابل ذخیره سازی از کلاس (create state) و از دیگری برای بازگردانی اطلاعات کلاس از یک نسخه قابل بازیابی استفاده میشه (restore).
  • کلاس Memento: این کلاس، کلاسی هست که ما اطلاعات کلاس فعلی خودمون رو در قالب این کلاس، ذخیره و بازیابی میکنیم. تمامی فیلد های مهم برای ما در عمل ذخیره و بازیابی، باید در این کلاس موجود باشن.
  • کلاس Caretaker: این کلاس، شامل یک ساختمان داده هست (معمولا لیست یا پشته) که نسخه های مختلف کلاس Originator در قالب کلاس Memento، در این کلاس ذخیره و بعدا در صورت نیاز از این کلاس خوانده میشن.

مدیریت تراکنش

مثال موجود در این پست، یک پیاده سازی بسیار ساده از الگوی طراحی Memento رو شامل میشه. یک کلاس داریم که اطلاعات حساب بانکی در اون نگهداری میشه و اسمش رو Account گذاشتم و این کلاس معادل Originator هست. این کلاس سه متد اصلی داره:

  • متد start: حالت فعلی کلاس رو در قالب یک کلاس Memento برمیگردونه. مثل BEGIN در تراکنش های دیتابیسی میمونه و حکم آغاز تراکنش ها رو داره.
  • متد END: دقیقا عملکرد متد start رو داره و اطلاعات نهایی کلاس رو در قالب Memento برمیگردونه.
  • متد rollback: این متد زمانی صدا میشه که در میان اجرای دستورات تراکنش، به یک exception برخوردیم و میخوایم تراکنش به حالت ابتدایی خودش یعنی حالتی که در start داشت برگرده. این متد، یک نمونه از Memento رو دریافت و کلاس Account رو بر اساس اون تغییر میده.

کلاس بعدی TransactionMemento هست. این کلاس معادل کلاس Memento و یک فیلد داره به اسم balance که دقیقا معادل همین فیلد در کلاس Account هست.

در نهایت، حالت های مختلف کلاس Account در قالب TransactionMemento در کلاس History ذخیره میشه. این کلاس معادل CareTaker در این الگو هست و یک لیست از TransactionMemento رو در خودش داره شامل دو متد اصلی push برای ذخیره یک memento در لیست و pop برای بازیابی آخرین memento از لیست است. در زیر، کلاس دیاگرام این پروژه رو با هم میبنیم:

کلاس دیاگرام الگوی Memento در پروژه مدیریت تراکنش
کلاس دیاگرام الگوی Memento در پروژه مدیریت تراکنش

پیاده سازی

قبل از اینکه شروع به پیاده سازی کنیم، این پروژه روی GitHub قرار گرفته و میتونید ساختارش رو بررسی و یا اجراش کنید.

پیاده سازی کلاس Account

https://gist.github.com/shahabes/a171d54a201fedc03a3cfa91a0429480

فیلد ها و متد های کلاس Account:

  • فیلد balance: مقدار موجودی حساب کاربر رو نگهداری میکنه.
  • متد deposit: یک عدد رو دریافت و در صورت مثبت بودن، اون رو به متغیر balance اضافه میکنه.
  • متد withdraw: یک عدد رو دریافت و در صورت مثبت بودن، اون رو از متغیر balance کم میکنه.
  • متد getBalance: مقدار فعلی حساب کاربر رو برمیگردونه.
  • متد start: یک نسخه Memento از اطلاعات حساب (که فقط فیلد balance) هست رو ایجاد و برمیگردونه.
  • متد end: دقیقا مثل متد start عمل میکنه.
  • متد rollBack: یک Memento رو دریافت و در کلاس فعلی قرار میده. از اونجا که در کلاس فعلی فقط balance مد نظر ما هست، پس در این متد فقط balance مقدار دهی شده.




پیاده سازی کلاس TransactionMemento

همانطور که قبل تر گفته شد، این کلاس باید مقادیری از کلاس Originator که در اینجا Account هست رو برای ذخیره سازی در CareTaker اماده کنه. در اصل، CareTaker فقط میتونه Memento رو ذخیره کنه.

https://gist.github.com/shahabes/d7b93001fe5f2df466f1ab7553674fc8

فیلد ها و متد های کلاس TransactionMemento:

  • فیلد balance: مقدار فعلی balance از کلاس Account در این متغیر قرار میگرد.
  • متد های settr/getter: این متد ها، ستر گترِ فیلد balance هستن.



پیاده سازی کلاس History

لیست حالت های مختلف از Memento در این کلاس قرار میگیره.

https://gist.github.com/shahabes/59c37733b759dc284d2460f4f6088d14

فیلد ها و متد های کلاس History:

  • فیلد transactionMemento: یک لیست از memento ها هست. وضعیت های مختلف کلاس Account در زمان اجرا، در این فیلد ذخیره خواهند شد.
  • متد push: از اسمش مشخص هست. یک Memento دریافت میکنه و ذخیره اش میکنه. توی مثال مدیریت تراکنش، طبیعتا این Memento باید یک حالت پایدار از کلاس باشن.
  • متد pop: این متد هم آخرین حالت کلاس(اخرین Memento ذخیره سازی شده) رو از لیست برمیداره و حذفش میکنه.(اگر نیاز به پیاده سازی قابلیت Redo در نرم افزار خودتون دارید، فقط index رو تغییر بدید و ایتم رو حذف نکنید.)



استفاده از الگوی Memento در برنامه

خب، تا به اینجا ساختار الگوی Memento رو با هم پیاده کردیم. ولی حالا چطوری ازش استفاده کنیم؟ من یک فایل دارم که منطق برنامه داخلش پیاده سازی شده و میخوام تعداد زیادی مبلغ رو به حساب مشتری واریز کنم و طبیعتا این مقادیر باید در قالب تراکنش انجام بشن. یعنی فقط و فقط زمانی کار با موفقیت انجام میشه که تمامی اون مقادیر با موفقیت به حساب کاربر واریز یا برداشت بشن و مثلا اگر حین انجام دستورالعمل ها، خطایی ایجاد بشه، باید به حالت قبل از اولین دستورالعمل یعنی زمانی که تراکنش شروع شده برگردیم. کلاس اصلی برنامه من، RollbackManager.java هست.

https://gist.github.com/shahabes/5afdff20afca58b7ad50a03f5470bbd8

همونطور که مشخص هست، یک ارایه به اسم 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 استفاده کردیم.

امیدوارم براتون مفید باشه.

design patternsjavaالگوهای طراحیجاوا
۴
۰
شهاب اسکندری
شهاب اسکندری
شاید از این پست‌ها خوشتان بیاید