اصول SOLID پنج اصل کلیدی در طراحی نرمافزار شیگرا هستند که هدف آنها ایجاد کدی قابل نگهداری، توسعهپذیر، و خوانا است. این اصول عبارتاند از:
- S: اصل مسئولیت یگانه (Single Responsibility Principle) – هر کلاس باید تنها یک مسئولیت داشته باشد و فقط به یک دلیل تغییر کند.
- O: اصل باز-بسته بودن (Open/Closed Principle) – کلاسها باید برای توسعه باز و برای تغییر بسته باشند، یعنی بتوانیم بدون تغییر در کد موجود، ویژگی جدید اضافه کنیم.
- L: اصل جایگزینی لیسکوف (Liskov Substitution Principle) – کلاسهای فرزند باید بتوانند جایگزین کلاسهای والد خود شوند بدون اینکه رفتار سیستم را خراب کنند.
- I: اصل تفکیک واسطها (Interface Segregation Principle) – نباید کلاسها را مجبور کنیم متدهایی را پیادهسازی کنند که به آنها نیازی ندارند؛ واسطها باید کوچک و اختصاصی باشند.
- D: اصل وارونگی وابستگی (Dependency Inversion Principle) – ماژولهای سطح بالا نباید مستقیماً به ماژولهای سطح پایین وابسته باشند؛ هر دو باید به انتزاعها وابسته باشند.
-
کلاس بزرگ با وظایف متعدد
کلاسPaymentProcessorهمزمان مسئول ولیدیشن، پردازش پرداخت و لاگینگ است. -
سوئیچهای بزرگ
متدهایprocessPaymentوvalidatePaymentاز سوئیچ بر اساس نوع پرداخت استفاده میکنند و افزودن روش جدید را دشوار میسازند. -
مقادیر ثابت و رشتههای جادویی
ارزها ("USD","EUR","GBP") و کلیدهایی مثل"card_number"،"wallet_id"و ... در کد به صورت هاردکد قرار داده شدهاند. -
متدهای چندمنظوره
متدی مانندprocessPaymentهمزمان ولیدیشن، پردازش و لاگینگ را انجام میدهد که اصل تکوظیفگی را زیر سؤال میبرد.
کلاس PaymentProcessor علاوه بر مسئولیت اصلی پردازش پرداخت، وظایف دیگری نظیر لاگینگ و اعتبارسنجی را نیز بر عهده دارد. این مسئله باعث میشود هر تغییری در یکی از این وظایف بر کل کلاس اثر بگذارد و پیچیدگی و هزینهی نگهداری را بالا ببرد.
در متدهای processPayment و validatePayment، برای تشخیص نوع پرداخت از ساختار سوئیچ استفاده شده است. برای افزودن یک روش پرداخت جدید (مثلاً پرداخت با ارز دیجیتال)، باید این متدها را ویرایش کرد. این بدان معناست که کلاس برای افزودن ویژگی جدید باز است اما نیازمند تغییر در کد فعلی است که مغایر با اصل OCP میباشد.
در این کد Interface تعریف نشده است. اگر بخواهیم یک اینترفیس واحد بسازیم که متدهای انواع پرداخت (کارت اعتباری، کیف پول دیجیتال و انتقال بانکی) را در بر داشته باشد، کلاسهایی که تنها بخشی از این متدها را نیاز دارند، بیهوده ملزم به پیادهسازی سایر متدها خواهند شد. این کار با اصل ISP در تضاد است. راهکار آن، تعریف اینترفیسهای مجزا برای هر نوع پرداخت یا حداقل تفکیک رفتارهاست.
کلاس PaymentProcessor بهصورت مستقیم با جزییات ماژولهای سطح پایین همچون APIهای کارت اعتباری، کیف پول دیجیتال و انتقال بانکی کار میکند (در متدهایی مانند processCreditCard، processDigitalWallet و processBankTransfer).
در نمونه کد داشده شده نقض این قانون مشاهده نمی شود.
- تعریف ویژگیهای مشترک مانند مبلغ، ارز و زمان
- ایجاد متد انتزاعی
validatePayment()برای اعتبارسنجی
CreditCardPayment: برای پرداختهای کارت اعتباریDigitalWalletPayment: برای پرداختهای کیف پول دیجیتالBankTransferPayment: برای پرداختهای انتقال بانکی
- ایجاد کلاس
PaymentFactoryبرای تولید انواع پرداخت - جداسازی منطق ایجاد اشیاء از سیستم پردازش
- استفاده از فکتوری برای ایجاد اشیاء پرداخت
- واگذاری منطق اعتبارسنجی به زیرکلاسهای پرداخت
- جداسازی منطق پردازش از اعتبارسنجی
در این فاز، با پیادهسازی رابط PaymentGateway و اعمال اصول وراثت و Polymorphism، سیستم پرداخت را توسعه دادیم.
- تعریف متودهای اصلی:
process،refundPaymentوgetTransactionStatus - ایجاد قرارداد مشخص برای درگاههای پرداخت
- فراهم کردن عملکرد مشترک برای تمام درگاهها
- مدیریت پیکربندی نقطه پایانی و لاگینگ
- پیادهسازی پیشفرض
getTransactionStatus
StripeGateway: پیادهسازی پردازش پرداخت از طریق StripePayPalGateway: پیادهسازی پردازش پرداخت از طریق PayPal
- امکان انتخاب درگاه پرداخت در زمان اجرا
- پیادهسازی Polymorphism با استفاده از رابط
PaymentGateway - ارسال درخواست پرداخت به درگاه انتخاب شده
پیادهسازی وراثت و Polymorphism از طریق رابط PaymentGateway امکان انتخاب و تعویض درگاههای پرداخت در زمان اجرا را فراهم میکند. این طراحی، سیستم را برای تغییرات آینده آماده میسازد و انعطافپذیری آن را هم افزایش میدهد.
- خواندن مقادیر پیکربندی از فایل
app.properties. - بارگذاری مقادیر مورد نیاز برای ساخت درگاههای پرداخت (endpoint، کلیدها و شناسهها).
- شامل مقادیر پیکربندی مانند:
paypal.api.url = https://api.paypal.com paypal.client.secret = paypal_client_secret paypal.client.id = paypal_client_id stripe.api.url = https://api.stripe.com stripe.api.key = stripe_api_key
با استفاده از Dependency Injection، لیست درگاههای پرداخت بهصورت پارامتر به کلاس PaymentProcessor تزریق شد؛ این کار باعث حذف وابستگی مستقیم به کلاسهای خاص، افزایش انعطافپذیری سیستم و سادهتر شدن تست و نگهداری کد گردید.