اصل Open/Closed یا باز/بسته چیست؟
در دنیای برنامهنویسی، شاید برایتان جالب باشد بدانید که اصل Open/Closed از اصول SOLID به برنامهنویسان میگوید که کدهایشان باید “برای توسعه باز و برای تغییر بسته” باشد. این یعنی شما باید قادر باشید که بدون تغییر کدهای موجود، قابلیتهای جدیدی به برنامه اضافه کنید.برای درک بهتر، بیایید این مفهوم را با یک مثال در پایتون بررسی کنیم:
مثال ۱: سیستم پیشنهادات محصول در فروشگاه آنلاین
فروشگاههای آنلاین مانند آمازون از سیستمهای پیشنهادات استفاده میکنند که به کاربران بر اساس تاریخچه خرید، رفتار مرور محصولات، و فصل سال پیشنهادات ویژهای ارائه میدهند. این پیشنهادات میتواند شامل “محصولات مشابه”، “محصولات پیشنهادی بر اساس فصل”، یا “محصولات پرطرفدار” باشد.
با رعایت اصل Open/Closed، اگر قرار باشد پیشنهادات دیگری اضافه شود، مانند “محصولات بر اساس حراجهای نزدیک” یا “محصولات بر اساس نیازهای مشتری جدید”، این پیشنهادات جدید به سیستم افزوده میشود، بدون نیاز به تغییر در الگوریتمهای اصلی. هر پیشنهاد بهصورت جداگانه به سیستم معرفی میشود، و سیستم اصلی بدون تغییر و پایدار باقی میماند.
مثال ۲: سیستم اطلاعرسانی در اپلیکیشن بانکی
اپلیکیشنهای بانکی میتوانند به کاربران از روشهای مختلفی اطلاعرسانی کنند: مانند ارسال پیامک، ایمیل، و نوتیفیکیشن درون برنامهای. با استفاده از اصل Open/Closed، هر روش اطلاعرسانی میتواند بهصورت یک گزینه مجزا در سیستم اضافه شود.
اگر نیاز به افزودن یک روش جدید باشد، مثلاً ارسال اعلانها از طریق پیامرسانها (مثل واتساپ)، به جای تغییر در سیستم اصلی اطلاعرسانی، این روش جدید به عنوان یک گزینهی جدید به سیستم اطلاعرسانی معرفی میشود. به این ترتیب، سیستم برای گسترش باز است، ولی برای تغییر در ساختار اصلی بسته.
مثال ۳: سیستم مدیریت پرداخت
فرض کنید در حال توسعهی یک سیستم پرداخت هستیم که نیاز دارد تا با روشهای مختلف پرداخت (مانند کارت اعتباری و پرداخت آنلاین) کار کند. کد زیر را ببینید:
class PaymentProcessor:
def process_credit_card(self, amount):
print(f"پرداخت {amount} تومان با کارت اعتباری انجام شد.")
def process_online_payment(self, amount):
print(f"پرداخت {amount} تومان به صورت آنلاین انجام شد.")
تا اینجا همه چیز خوب به نظر میرسد. ولی فرض کنید یک روش جدید پرداخت اضافه کنیم، مثلاً پرداخت با کیف پول دیجیتال! حالا ما مجبور میشویم کلاس را تغییر دهیم و این تغییر باعث میشه قبل از هرچیزی تابع های قبلی رو خونده باشیم و مجدد اونهارو تست کنیم و . این نقض اصل Open/Closed است، چون داریم کد موجود را تغییر میدهیم و احتمال ایجاد خطا و مشکل در سیستم به شدت بالا میره.
اجرای اصل Open/Closed: استفاده از کلاسهای abstract
برای پیروی از این اصل، به جای اضافه کردن روشهای پرداخت مستقیم به کلاس، هر روش را به عنوان یک کلاس مجزا تعریف میکنیم:
from abc import ABC, abstractmethod
class PaymentMethod(ABC):
@abstractmethod
def pay(self, amount):
pass
class CreditCardPayment(PaymentMethod):
def pay(self, amount):
print(f"پرداخت {amount} تومان با کارت اعتباری انجام شد.")
class OnlinePayment(PaymentMethod):
def pay(self, amount):
print(f"پرداخت {amount} تومان به صورت آنلاین انجام شد.")
class WalletPayment(PaymentMethod):
def pay(self, amount):
print(f"پرداخت {amount} تومان از کیف پول دیجیتال انجام شد.")
حالا کلاس اصلی ما
حالا کلاس اصلی، PaymentProcessor، به جای اینکه متدها را مستقیماً داشته باشد، از کلاسهای فرعی استفاده میکند:
class PaymentProcessor:
def process_payment(self, payment_method: PaymentMethod, amount):
payment_method.pay(amount)
و در نهایت استفاده از این کد به این شکل است:
processor = PaymentProcessor()
processor.process_payment(CreditCardPayment(), 1000)
processor.process_payment(OnlinePayment(), 2000)
processor.process_payment(WalletPayment(), 1500)
مزایای این ساختار چیست؟
با این ساختار، اگر نیاز به اضافه کردن روش جدید پرداخت باشد، کافی است یک کلاس جدید مانند CryptoPayment ایجاد کرده و بدون دست زدن به PaymentProcessor آن را به کد اضافه کنیم.
کد شما حالا مثل یک در بسته است، نمیتوانید به زور در را باز کنید و تغییراتتان را به آن تحمیل کنید! فقط کلیدهای جدید میسازید و هر جا لازم باشد با آنها کار میکنید.
