OCP (Open/Closed Principle) — دومین اصل SOLID که تعیین میکند: موجودیتهای نرمافزاری باید برای توسعه باز، اما برای تغییر بسته باشند. این اصل که توسط برتراند مایر در سال 1988 فرموله شد، امکان افزودن قابلیت جدید بدون تغییر کد موجود را فراهم میکند. بر اساس کتاب رابرت مارتین Clean Architecture (2017)، اصل باز بودن از طریق انتزاع و چندریختی پیادهسازی میشود و خطر خطاهای بازگشتی را به حداقل میرساند.
نکات اصلی
OCP (Open/Closed Principle) — اصل باز بودن برای توسعه و بسته بودن برای تغییر است. کلاسها، ماژولها و توابع باید طوری طراحی شوند که رفتار جدید بدون تغییر کد منبع آنها اضافه شود. توسعه از طریق وراثت، ترکیب یا جایگزینی پیادهسازیهای رابطها به دست میآید.
برتراند مایر در کتاب Object-Oriented Software Construction (1988) برای اولین بار OCP را از طریق وراثت توصیف کرد: کلاس پایه بدون تغییر میماند و زیرکلاسها رفتار آن را توسعه میدهند. تفسیر مدرن OCP که توسط رابرت مارتین ارائه شده، بر چندریختی و رابطها تکیه دارد: به جای وراثت از قراردادهای انتزاعی استفاده میشود.
تفاوت رویکردها قابل توجه است. وراثت یک پیوند سفت بین کلاس پایه و مشتق ایجاد میکند. رابطها و ترکیب انعطافپذیری میدهند: پیادهسازی بدون تغییر کد مشتری جایگزین میشود. OCP مدرن — درباره انتزاع است، نه وراثت.
OCP چندریختی از کلاسهای انتزاعی یا رابطها برای تعریف قرارداد استفاده میکند. کد مشتری با انتزاع کار میکند و از پیادهسازی مشخص بیخبر است. قابلیت جدید با ایجاد یک کلاس جدید که همان رابط را پیادهسازی میکند اضافه میشود — بدون هیچ تغییری در کد موجود. این سیستم را در برابر تغییرات مقاوم و برای توسعه قابل پیشبینی میکند.
در توسعه موبایل این رویکرد همهگیر است: الگوی Strategy امکان جایگزینی الگوریتمها (فشردهسازی تصاویر، کش کردن، احراز هویت) را از طریق یک رابط مشترک فراهم میکند. افزودن یک استراتژی جدید نیازی به تغییر کدی که از آن استفاده میکند ندارد.
پیادهسازی OCP با جدا کردن رفتار متغیر به یک انتزاع آغاز میشود. اگر در کد ساختار switch یا زنجیره if-else وجود دارد که نوع شیء را بررسی میکند — این نشانهای برای اعمال OCP است. هر شاخه شرط به طور بالقوه هنگام توسعه نیاز به افزودن یک شاخه جدید دارد.
فرایند بازسازی (رفکتورینگ) مطابق با OCP شامل سه مرحله است: جنبه متغیر را مشخص کنید (چیزی که ممکن است توسعه یابد)، آن را به یک رابط یا کلاس انتزاعی جدا کنید، کد مشتری را برای کار با انتزاع به جای کلاس مشخص بازنویسی کنید. پس از آن قابلیت جدید بدون تغییر مشتری اضافه میشود.
نکته مهم: بسته بودن برای تغییر مطلق نیست. اگر نیازمندی به خود انتزاع یا قرارداد مربوط باشد — تغییر اجتنابناپذیر است. OCP از تغییرات در پیادهسازیها محافظت میکند، نه در قراردادها. طراحی خوب فرض میکند که قراردادها پایدار و پیادهسازیها متغیر هستند.
هنگام ارزیابی سازگاری معماری با OCP، نگاه کردن به نقاط توسعه مفید است. هر نقطهای که توسعهدهنده if-else یا switch برای یک نوع جدید اضافه میکند — کاندیدای انتزاع است. سیستمی که مطابق با OCP طراحی شده باشد دارای نقاط توسعه قابل پیشبینی است: رابطهایی با مستندات «این رابط را پیادهسازی کن تا نوع جدید اضافه شود». در Android نمونهای از این الگوی Factory همراه با ViewModelProvider.Factory است — افزودن نوع جدید ViewModel نیازی به تغییر کارخانههای موجود ندارد.
موثرترین الگوها برای رعایت OCP در توسعه موبایل شامل Strategy، Template Method، Decorator و Factory است. هر کدام مشکل توسعه رفتار بدون تغییر کد موجود را از طریق مکانیزمهای مختلف طراحی شیءگرا حل میکنند.
Strategy امکان جایگزینی الگوریتمها در لحظه از طریق یک رابط مشترک را فراهم میکند. در توسعه iOS از استراتژیها برای انیمیشنها و اعتبارسنجی فرمها استفاده میشود. Template Method اسکلت الگوریتم را در کلاس پایه تعریف میکند و زیرکلاسها مراحل را بازنویسی میکنند — مناسب برای صفحات با ساختار مشترک اما محتوای متفاوت.
Decorator به طور پویا به یک شیء بدون تغییر کلاس آن رفتار اضافه میکند. در Android از Decorator برای پیچیدن Repository با لایه کش یا ثبت وقایع استفاده میشود. Factory Method اشیاء را از طریق رابط ایجاد میکند و به زیرکلاسها اجازه میدهد تصمیم بگیرند کدام کلاس را نمونهسازی کنند — اساس ایجاد وابستگی سازگار با OCP.
انتخاب الگو به پایداری رفتاری که توسعه مییابد بستگی دارد. Strategy زمانی بهینه است که الگوریتمها به طور کامل جایگزین میشوند. Template Method — زمانی که ساختار ثابت اما مراحل متغیر هستند. Decorator — زمانی که توسعه باید برای مشتری شفاف باشد. برای بیشتر سناریوها در Android و iOS Strategy + تزریق وابستگی کافی است.
اعمال این الگوها بدون OCP از نظر فنی ممکن است اما بیمعنی است. این OCP است که توجیه میکند چرا یک سطح انتزاع اضافی معرفی میکنیم: تا سیستم بدون بازنویسی کد موجود رشد کند.
مثال Android با پردازش پرداختها را در نظر بگیرید. بدون OCP هر سیستم پرداخت جدید نیاز به تغییر کلاس پردازشگر دارد. با OCP یک پیادهسازی جدید از رابط بدون ویرایش کد موجود اضافه میشود.
// نقض OCP: switch نیاز به ویرایش برای سیستم جدید دارد
class BadPaymentProcessor {
fun process(type: String) {
when (type) {
"card" -> // پردازش کارت
"paypal" -> // پردازش PayPal
}
}
}
// طراحی سازگار با OCP
interface PaymentMethod {
fun pay(amount: Double)
}
class CardPayment : PaymentMethod {
override fun pay(amount: Double) { }
}
class PayPalPayment : PaymentMethod {
override fun pay(amount: Double) { }
}
// سیستم جدید — کلاس جدید، بدون تغییر کد موجود
class ApplePayPayment : PaymentMethod {
override fun pay(amount: Double) { }
}
مثال iOS با اعتبارسنجی فیلدهای متنی همان منطق را از طریق پروتکلهای Swift نشان میدهد:
// اعتبارسنجی سازگار با OCP
protocol ValidationRule {
func validate(_ input: String) -> Bool
}
struct EmailRule: ValidationRule {
func validate(_ input: String) -> Bool {
return input.contains("@")
}
}
struct PhoneRule: ValidationRule {
func validate(_ input: String) -> Bool {
return input.count == 11
}
}
// افزودن قانون جدید نیازی به تغییر کد اعتبارسنج ندارد
struct PasswordRule: ValidationRule {
func validate(_ input: String) -> Bool {
return input.count >= 8
}
}
مزیت کلیدی OCP در این مثالها: افزودن ApplePay یا PasswordRule نیازی به تغییر کلاسهای موجود ندارد. کد به صورت افقی توسعه مییابد — از طریق فایلهای جدید، نه با تغییر فایلهای قدیمی. این خطر بازگشت را کاهش میدهد و پیادهسازی قابلیت جدید را تسریع میکند.
رایجترین نقض — ساختار switch یا when بر اساس نوع شیء. هر بار که یک نوع جدید اضافه میشود باید همه این switchها را در کد پیدا کرد و یک شاخه جدید اضافه کرد. یک switch از قلم افتاده — یک باگ در زمان اجرا که در مرحله کامپایل به سختی قابل کشف است.
در توسعه موبایل OCP با استفاده از کلاسهای enum غولآسا با متدهایی که به مقدار enum وابسته هستند نقض میشود. افزودن یک عنصر enum جدید نیاز به تغییر هر switch در کل پروژه دارد. جایگزین — چندریختی از طریق رابط، جایی که هر نوع رفتار خود را پیادهسازی میکند.
نقض رایج دیگر — God Adapter: RecyclerView.Adapter (Android) یا UITableViewDataSource (iOS) که از طریق if-else انواع مختلف سلولها را پردازش میکند. هر نوع سلول جدید نیاز به توسعه آداپتور دارد. راهحل — ViewHolder چندریختی با متد bind مشترک، جایی که هر نوع سلول مسئول نمایش خود است.
اقدامات پیشگیرانه شامل: کنار گذاشتن switch بر اساس نوع به نفع چندریختی، تزریق وابستگی از طریق رابطها و استفاده از الگوی Factory برای ایجاد اشیاء بر اساس پیکربندی. تحلیل کد برای «سوئیچهای بر اساس نوع» — بخش الزامی بازبینی کد در تیمهای OCP-محور.
بازسازی نقض OCP موجود از طریق Replace Conditional with Polymorphism انجام میشود: هر شاخه شرط به یک کلاس جداگانه با پیادهسازی یک رابط مشترک تبدیل میشود. کد مشتری برای کار با رابط بازنویسی میشود و پیادهسازی مشخص از طریق یک کارخانه یا ظرف DI تأمین میشود.
درک این نکته مهم است که OCP و چندریختی همه مشکلات توسعه را حل نمیکنند. اگر معماری به اشتباه انتخاب شده باشد، افزودن قابلیت جدید نه تنها تغییر پیادهسازیها بلکه تغییر قراردادها را نیز نیاز خواهد داشت. معماری خوب جهتهای توسعه را پیشبینی میکند و دقیقاً در همان نقاط انتزاع ایجاد میکند. سرمایهگذاری در OCP هر چه پروژه بیشتر عمر کند و نیازمندیهای ماژولهای خاص بیشتر تغییر کند، بیشتر بازدهی دارد.
سوالات متداول
خیر. OCP تغییر کد موجود را هنگام افزودن قابلیت جدید مربوط به همان انتزاع منع میکند. تغییر قرارداد، رفع اشکالات و بازسازی نقض OCP محسوب نمیشوند — اصل از تغییرات آبشاری هنگام توسعه محافظت میکند.
Strategy — پیادهسازی مستقیم OCP است. رابط استراتژی قرارداد را تعریف میکند، مشتری به انتزاع وابسته است و استراتژیهای مشخص رفتار متغیر را پیادهسازی میکنند. افزودن یک استراتژی جدید نیازی به تغییر مشتری ندارد — این همان باز بودن برای توسعه در عین بسته بودن برای تغییر است.
بله، از طریق وراثت و Template Method: کلاس پایه اسکلت الگوریتم را تعریف میکند، زیرکلاسها مراحل را بازنویسی میکنند. با این حال وراثت یک پیوند سفت ایجاد میکند و انعطافپذیری کمتری نسبت به رابطها دارد. در توسعه مدرن، رابطها و ترکیب روش ترجیحی برای پیادهسازی OCP محسوب میشوند.
کد سازگار با OCP تستنویسی را آسانتر میکند: هر پیادهسازی رابط به صورت مجزا تست میشود. کد مشتری با پیادهسازی ساختگی (mock) تست میشود که امکان بررسی منطق بدون وابستگی به رفتار مشخص را فراهم میکند. توسعه سیستم نیازی به بازنویسی تستهای موجود ندارد.
خیر. OCP زمانی موجه است که توسعه قابلیت قابل پیشبینی باشد. برای کد پایداری که قرار نیست توسعه یابد، انتزاع اضافی زائد است. YAGNI (You Ain't Gonna Need It) — تعادل خوبی در برابر OCP است: انتزاع زمانی معرفی میشود که یک نوع رفتار دوم ظاهر شود، نه از قبل.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید