علی فرهادنیا
علی فرهادنیا
خواندن ۵ دقیقه·۱ سال پیش

اصول SOLID در زبان Go (قسمت دوم-Open/Closed)



مقدمه :

سلام

نوبتیم باشه نوبت این اصل خفنه

یه جورایی میشه گفت این اصل زیربنای طراحی نرم افزاره

کلا توی حوزه توسعه نرم افزار این اصل خیلی مهمه چون میتونه کلی پول و وقتو ... نجات بده.

میخوایم باهم اصل باز/بسته یا به قول خارجیا open/closed یا به طور خلاصه OCP رو بررسی کنیم.

این اصل به طور خلاصه میگه کد یا تابع یا کلا هر قسمتی که میخواین طراحی کنید ، یه شکلی طراحی کنید که:

  • در برابر توسعه باز باشه و بشه راحت با اضافه کردن کد اون رو توسعه داد
  • در برابر تغییر بسته باشه یعنی برای اضافه کردن یا کم کردن یک ویژگی لازم نباشه کد های نامربوط رو تغییر بدیم.

مراحل کارمون به این صورته:

  • اول این اصل رو با یک مثال زیر پا بذاریم.
  • بعد ببینیم چه مشکلاتی برامون ایجاد میکنه.
  • و بعد باهم مثالو باز نویسی کنیم و مشکلات رو حل کنیم و برسیم به اصل OCP.





بریم اول این اصل رو زیر پا بذاریم ببینیم چی میشه :)

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

اینا درگاه های پرداخت ما هستن:

  • Credit Card
  • PayPal
  • Bitcoin

برای سادگی کار فرض میکنیم فرایند پرداخت خلاصه میشه به یه تابع که یه مبلغ رو به عنوان ورودی میگیره و بر اساس متد پرداخت همه کارها رو انجام میده. کاش واقعا همینقدر ساده بود:/


type Payment struct { Method string } func (p *Payment) ProcessPayment(amount float64) error { switch p.Method { case &quotcredit_card&quot: // Credit card payment logic case &quotpaypal&quot: // PayPal payment logic case &quotbitcoin&quot: // Bitcoin payment logic default: return errors.New(&quotunsupported payment method&quot) } return nil }

خب کارمون تموم شد.




عا ما (بخونید آما)

دو روز دیگه میان میگن یه درگاه پرداخت دیگه وصل کن.

توی داستان ما میگن برامون درگاه stripe وصل کن .

خب میایم اضافه میکنیم:

func (p *Payment) ProcessPayment(amount float64) error { switch p.Method { case &quotcredit_card&quot: // Credit card payment logic case &quotpaypal&quot: // PayPal payment logic case &quotbitcoin&quot: // Bitcoin payment logic case &quotstripe&quot: // Stripe payment logic default: return errors.New(&quotunsupported payment method&quot) } return nil }

حالا مشکل کجاست؟

مشکل زیاده:

مشکل :
اصل تک وظیفه ای رو زیر پا میذاره.
چون این تابع دیگه فقط مسئول پردازش پرداخت نیست بلکه باید نحوه پرداخت هرکدوم از درگاه هارو هم بدونه.
مشکل:
تست کردن این کد به مرور سخت ترو سخت تر میشه .
چون هر بار که یک درگاه پرداخت اضافه میشه باید نه تنها خود درگاه رو تست کنیم بلکه تاثیرش روی بقیه درگاه
هارم تست کنیم.
مشکل :
اضافه کردن درگاه پرداخت رو سخت تر میکنه.
چون ممکنه روی بقیه تاثیر بذاره و این یعنی بعد یه مدت فاتحه سیستم رو باید خوند(توی بازار مجبوری در
زمان کوتاه فیچر های مختلف رو کمو زیاد کنی و بعد یه مدت یا دیگه نمیشه خیلی چیزی اضافه کرد یا با هر تغییر
اینقدر باگ میخوره سیستم که ...).
مشکل:
دیباگ کردنش به مرور با رشد سیستم خیلی سخت میشه.
هر سیستمی باگ داره و نیاز به نگهداری داره و نگهداری از کدی که شلوغ و کثیفه و هر روز کثیف تر میشه حقیقتا
خواب اروم شبو از ادم میگیره.
مشکل:
گفتن جمله " سیستم قابلیت اضافه کردن این فیچر رو نداره ".
اخ اخ اخ وقتی میرسی به این نقطه که میترسی دست ببری تو کد و چیزیو تغییر بدی یا اضافه کنی چون تا دست
ببری احتمال منفجر شدنش هست ...

خب این همه ناله کردیم حالا راه حل چیه؟

بیاین کد قبلیو دوباره بنویسیم ولی این بار با یک رویکرد متفاوت که اصل OCP(Open/Closed Principle) رو رعایت کنه:

میایم یه اینترفیس تعریف میکنیم برای درگاه پرداخت:

type PaymentMethod interface { ProcessPayment(amount float64) error }


میگیم هرکی میخواد درگاه پرداخت باشه باید تابع ProcessPayment رو پیاده سازی کنه.

حالا بریم درگاه پرداخت هامونو ایجاد کنیم:

Credit Card :

type CreditCard struct{} func (c CreditCard) ProcessPayment(amount float64) error { // Credit card payment logic }

PayPal :

type PayPal struct{} func (p PayPal) ProcessPayment(amount float64) error { // PayPal payment logic }

Bitcoin :

type Bitcoin struct{} func (b Bitcoin) ProcessPayment(amount float64) error { // Bitcoin payment logic }


دوباره پروسه پرداخت رو بنویسیم:

type Payment struct { Method PaymentMethod } func (p *Payment) ProcessPayment(amount float64) error { return p.Method.ProcessPayment(amount) }


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

بریم سراغ اضافه کردن درگاه پرداخت stripe:

type Stripe struct{} func (s Stripe ) ProcessPayment(amount float64) error { // Stripe payment logic }

خب اضافه شد به همین راحتی و بدون خونریزی.

دیگه لازم نیست برای هر بار اضافه کردن درگاه پرداخت بریم سراغ پروسه پرداخت و جراحیش کنیم.

همون گوشه کنارا یه کد برای درگاه پرداخت جدید مینویسیم و استفاده میکنیم.

یه جورایی انگار ماژولار شده کدمون.




توی مثال ما وقتی خواستیم درگاه پرداخت اضافه کنیم قسمت پروسه پرداخت دست نخورد و در برابر تغییر بسته بود.

اما راحت میشد درگاه اضافه کرد و دربرابر توسعه باز بود.

و این همون اصل باز/بستس

باز در برابر توسعه و بسته در برابر تغییر


نکته: این پیاده سازی خیلی ساده سازی شده بود و راه های بهتری هم برای این کار هست(مثلا اینکه از چند درگاه همزمان پشتیبانی کنه و با ورود اسم درگاه خود تابع با درگاه مورد نظر پرداختش کنه)

شاید بعدا درباره پیاده سازی بهتر همین کد مطلبی نوشتم:)




مرسی از وقتی که گذاشتین ...

لینکدین

گیتهاب

درگاه پرداختgolangsoftware engineeringsolidprogramming
برنامه نویس بک اند(ترجیحا با زبان گولنگ)-علاقه مند به اشتراک گذاری مطالبی که یاد میگیرم.
شاید از این پست‌ها خوشتان بیاید