Skip to content

پروژه ها و تکالیف درس طراحی سیستم های شی گرا

Notifications You must be signed in to change notification settings

amirhoseincodes/OOD-labs

Repository files navigation

🏨 سیستم رزرو هتل – تمرین اصول شی گرایی و SOLID

اولین تمرین درس طراحی شی‌گرای سیستم‌ها

هدف این پروژه، آشنایی عمیق‌تر با اصول شی‌گرایی (OOP) و تمرین عملی اصول SOLID از طریق تحلیل و اصلاح یک سیستم ساده رزرو هتل است. این پروژه یک پروژه تمرینی است و ساختار آن عمداً ساده طراحی شده است.


📌 معرفی پروژه

در این تمرین، یک سیستم ساده رزرو اتاق هتل قرار دارد. این سیستم شامل کلاس‌های زیر است:

  • Customer → نمایش اطلاعات مشتری

  • Room → اطلاعات اتاق

  • Reservation → ذخیره اطلاعات رزرو

  • ReservationService → مدیریت فرآیند رزرو (متد makeReservation)

  • EmailSender → ارسال تأییدیه رزرو

  • PaymentProcessor → انتخاب و اجرای روش پرداخت

در نسخه اولیه‌ی پروژه، متد رزرو یک مشتری را ایجاد می‌کند، یک اتاق تخصیص می‌دهد و با توجه به روش پرداخت انتخابی، عملیات رزرو را انجام داده و در پایان، تاییدیه را از طریق ایمیل ارسال می‌کند.

کد اولیه پروژه از لینک زیر قابل دریافت است:

https://github.com/yahyaPoursoltani/base-project-for-solid/tree/master


گام اول: افزودن دو قابلیت جدید

در این مرحله لازم است یک نسخه از پروژه تهیه کرده و در پوشه‌ی
Step-01-without-OOD/
ذخیره کنید. سپس دو قابلیت جدید زیر را به سیستم اضافه نمایید:

1️⃣ افزودن روش جدید ارسال پیام

افزودن ارسال پیامک (SMS) با ایجاد کلاسی مشابه EmailSender با نام:
SmsSender

2️⃣ افزودن روش جدید پرداخت

افزودن روش جدید پرداخت حضوری توسط اصلاح کلاس:
PaymentProcessor
و افزودن متدی مانند:
onSitePayment()


جدول گزارش تغییرات

ردیف تغییرات مرتبط با ارسال پیام جدید کلاس تغییر یافته توضیح تغییر تغییرات مرتبط با پرداخت جدید کلاس تغییر یافته توضیح تغییر
1 تغییر نام تابع sendEmail به send MessageSender قابل استفاده بودن تابع برای هم ارسال پیامک و هم ایمیل افزودن INPERSON به enum PaymentMethods اضافه‌کردن گزینه پرداخت حضوری
2 افزودن SMS به switch case ReservationService چاپ پیام تایید رزرو با پیامک افزودن متود payInPerson PaymentProcessor متود جدید برای چاپ کردن روش پرداخت
3 افزودن this.notifier=notifier به makeReservation ReservationService جهت گرفتن notifier از ورودی و بررسی در switch case اضافه کردن INPERSON به switch case ReservationService این قسمت payment processor را از قسمت قبلی صدا می زند

همچنین کلاس PoorRoom به برنامه اضافه شد تا گزینه ای مناسب برای افرادی که تمکن مالی بالایی ندارند باشد.

همچنین کلاس SmsSender به برنامه اضافه شد تا یک کلاس برای ارسال پیامک داشته باشیم.


گام دوم : تحلیل اصول شی گرایی

در ادامه این تغییرات را از نظر رعایت اصول شی‌گرایی تحلیل خواهیم کرد.
این گام مقدمه‌ای است برای بررسی:

  • نقض‌های احتمالی اصول SOLID

  • شناسایی وابستگی‌های نامناسب

  • بهبود طراحی سیستم

اصل کلاس علت برقراری / نقض
اصل SRP مورد برقراری LuxuryRoom و PoorRoom می توان گفت که توابع AddFreeDinner و AddFreeRommate به عنوان رفتار طبیعی برای هر نوع اتاق تعریف شده اند و single responsibility در این کلاس ها برای صرفا نمونه سازی اشیا همچنان برقرار است.
اصل SRP مورد نقض LuxuryRoom و PoorRoom میتوان گفت توابع AddFreeDinner و AddfreeRommate ارتباطی به مسئولیت این کلاس ها ندارند و می توانند در قالب یک کلاس جدا مثلا RoomBenefits تعریف بشوند.
اصل OCP مورد برقراری MessageSender این کلاس با دارا بودن چارچوب اصلی یک ارسال کننده پیام برای اضافه کردن روش ها و ارث بری کلاس های جدید باز و دربرابر تغییرات آنها بسته است.
اصل OCP مورد نقض PaymentProcessor و ReservationService PaymentProcessor تمامی متد های پرداخت را دربر گرفته است که باعث می شود قابلیت باز بودن در برابر تغییرات را از دست بدهد و در اضافه شدن هر متد نیازمند ویرایش باشد. همین وضعیت به شکل بدتری در ReservationService است و حالت بندی های مختلفی برای نوع پرداخت و روش اطلاع رسانی وجود دارد.
اصل LSP مورد برقراری MessageSender و فرزندانش هر کدام از کلاس های SmsSenderوEmailSenderمی توانند بدون مشکل جای پدر بنشینند.
اصل LSP مورد نقض 1 Room امکان تعیین type باعث می شود که در صورت استفاده کردن از کلاس فرزند بتوان نوع آن را تغییردهد در حالیکه شی استفاده شده از کلاس فرزند با آن یکی نباشد! پس نوع اتاق نباید یک رشته در کلاس پایه باشد.
اصل LSP مورد نقض 2 PaymentProcessor رابط های (متود ها) بیخودی فراوان هر بار که بخواهیم پرداخت کنیم ناگزیر اضافه می شوند!
اصل ISP مورد برقراری MessageSender از آنجا که هر کدام از روش های اطلاع رسانی توابع خاص خود را دارند پس ارث بری از MessageSender در EmailSender و SmsSender محل درست رعایت ISP است.
اصل ISP مورد نقض PaymentProcessor در این کلاس چهار متد پرداخت متفاوت گذاشته شده است و اگر نیاز به فقط یکی از آن ها داشته باشیم مجبوریم به سه متد دیگر هم وابسته باشیم.
اصل DIP مورد برقراری SmsSender این کلاس فقط به اینترفیس MessageSender وابستگی دارد نه کلاس concrete دیگری.
اصل DIP مورد نقض ReservationService کلاس در متد makeReservation هم paymentType و هم notifier را از ورودی می گیرد که این باعث می شود وابستگی آن به سمت کلاس‌های جزئی که تغییر می‌کنند برود. (باید صرفا متد را از شی مربوطه فراخوانی کند) کلاس PaymentProcessor خودش زمینه این مشکل را فراهم کرده است.(مشکلات امنیتی که بماند.)
اصل PLK مورد برقراری Reservation دسترسی به فیلد ها به صورت ساده و کاملا درست داده شده است.
صل PLK مورد نقض ReservationService زنجیره طولانی و بی فایده res.customer.city و res.room.price وابستگی(coupling) کد را بالا برده.
اصل CRP مورد برقراری Reservation اینکه یک شی از کلاس های Room و Customer را در خودش دارد یعنی فقط از آن ها استفاده می کند و ارث بری بیخودی برای استفاده مجدد ندارد.
اصل CRP مورد نقض ReservationService همه چیز مانند پرداخت ، رزرو و ارسال پیام در پکیج services گذاشته شده است و اگر کلاسی مانند ReservationService به یکی از آن ها فقط نیاز داشته باشد باید بقیه را هم ارث ببرد.

گام سوم : اصلاح موارد نقض

نقض اصل SRP با تقسیم کلاس ReservationService به چهار سرویس تک‌مسئولیتی شامل DiscountService، InvoicePrinter، NotificationService و ReservationService صرفا هماهنگ‌کننده رفع شد. همچنین کلاس PaymentProcessor حذف و هر روش پرداخت به یک کلاس مستقل تبدیل شد.

نقض اصل OCP با پیاده‌سازی پترن Strategy همراه با یک متد factory برای پرداخت و نوتیفیکیشن برطرف شد. به این ترتیب افزودن روش پرداخت یا پیام جدید تنها با ایجاد یک کلاس جدید انجام می‌شود و هیچ‌یک از کلاس‌های موجود نیاز به تغییر ندارند.

نقض اصل DIP با تعریف اینترفیس‌های جدا برای هر سرویس و انجام پیاده‌سازی‌های مربوطه از طریق constructor کلاس برطرف شد. در نتیجه ماژول‌های سطح بالا تنها به abstraction وابسته هستند.

نقض اصل PLK با کپسوله‌سازی تمامی دسترسی‌ها به فیلدهای آبجکت های Customer و Room در داخل کلاس Reservation و ارائه متدهای کمکی مناسب رفع شد. اکنون هیچ کلاس دیگری به ساختار داخلی آبجکت های داخلی دسترسی مستقیم ندارد و تمام تعاملات تنها از طریق ابجکت Reservation انجام می‌شود.

نقض اصل LSP با تنظیم فیلد type به کمک private final و مقدار دهی فقط در constructor انجام شد. همچنین متود های get برای هر یک از فیلد ها تعریف شد و مشکل تداخل تغییر نوع غیر مجاز از این طریق برطرف شد.

نقض اصل ISP توابع از paymentProcessor خارج شدند و paymentstrategy به عنوان تنها interface برای پیاده سازی متود pay پیاده سازی شد. الان اضافه کردن هر تعداد روش با کمترین تغییر امکان پذیر است.

نقض اصل CRP تعیین قیمت نهایی به اینترفیس جدای IDiscountService واگذاری شد و چاپ رسید رزرو و ارسال نوتیفیکیشن هم به IInvoicePrinter و INotificationService واگذاری شدند. همچنین پکیج های notification, payment و reservation از services مشتق شدند و import ها کاهش پیدا کردند.


گام چهارم : ارزیابی

اگر از همان ابتدا اصول شی گرایی به‌درستی در این پروژه رعایت شده بود، تقریبا تمام دردسرها و تغییراتی که در مرحله اول برای افزودن پیامک و یک روش پرداخت جدید متحمل شدیم، وجود نمی‌داشت. در حالت عادی مجبور شدیم چندین فایل را باز کنیم، switchها را دستکاری کنیم، نام متدها را تغییر دهیم، باگ‌های مربوط به استفاده نکردن از پارامترها را رفع کنیم. کاری که در مجموع ۷ یا ۸ تغییر بود.

اما اگر پروژه از روز اول بر پایه اصول درست طراحی شده بود، برای اضافه کردن هر دو قابلیت (پیامک و روش پرداخت جدید) فقط دو کار بسیار ساده کافی بود: ۱. یک کلاس جدید بسازیم (مثلاً SmsSender و InPersonPayment). ۲. در صورت نیاز، فقط یک خط به بخش factory اضافه کنیم یا حتی همان را هم لازم نباشد دست بزنیم. اینطوری نه سوئیچی تغییر می‌کرد، نه کلاس قدیمی بازنویسی می‌شد، نه ریسک خراب شدن بقیه قسمت‌ها وجود داشت. به بیان دیگر، رعایت اصول شی گرایی از ابتدا باعث می‌شد به‌جای چندین تغییر پراکنده و حساس، هر قابلیت جدید فقط با یک یا حداکثر دو تغییر کوچک و ایمن اضافه شود. این دقیقا همان چیزی است که در پروژه‌های واقعی، نگهداری و توسعه را آسان، سریع و کم‌هزینه می‌کند.


گام پنجم : نتیجه گیری

رعایت اصول شی گرایی در این پروژه باعث شد کد از یک ساختار درهم و ضعیف به کدی منظم، خوانا و انعطاف پذیر تبدیل شود. هر بخش اکنون تنها یک وظیفه مشخص دارد، افزودن قابلیت های جدید بدون تغییر کدهای قبلی ممکن شده و وابستگی‌ها نیز به صورت کنترل شده و قابل جایگزینی مدیریت می‌شوند.

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


🧩 نکات پایانی

دل گرچه درین بادیه بسیار شتافت

یک موی ندانست و بسی موی شکافت

گرچه ز دلم هزار خورشید بتافت

آخر به کمال ذره‌ای راه نیافت

https://ganjoor.net/abusaeed/robaee-aa/sh165

About

پروژه ها و تکالیف درس طراحی سیستم های شی گرا

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages