OCP (مبدأ الفتح/الإغلاق) هو المبدأ الثاني من SOLID، والذي ينص على: يجب أن تكون الكيانات البرمجية مفتوحة للتمديد ولكن مغلقة للتعديل. هذا المبدأ، الذي صاغه برتراند ماير في عام 1988، يسمح بإضافة وظائف جديدة دون تغيير الكود الموجود. وفقًا لكتاب روبرت مارتن Clean Architecture (2017)، يتم تطبيق مبدأ الانفتاح من خلال التجريدات وتعدد الأشكال، مما يقلل من خطر أخطاء الانحدار.
الملخص
OCP (مبدأ الفتح/الإغلاق) — مبدأ الانفتاح للتمديد والإغلاق للتعديل. يجب تصميم الفئات والوحدات والوظائف بحيث يمكن إضافة سلوك جديد دون تغيير كودها المصدر. يتم تحقيق التمديد من خلال الوراثة أو التركيب أو استبدال تطبيقات الواجهات.
برتراند ماير في كتابه 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 يبسط الاختبار: كل تطبيق واجهة يختبر بشكل منعزل. يختبر كود العميل مع تطبيق وهمي، مما يسمح بالتحقق من المنطق دون الارتباط بسلوك محدد. توسيع النظام لا يتطلب إعادة كتابة الاختبارات الموجودة.
لا. OCP مبرر عندما يكون التمديد الوظيفي متوقعًا. للكود المستقر الذي لا يخطط لتمديده، التجريد الإضافي مفرط. YAGNI (لن تحتاجه) هو توازن جيد لـ OCP: يتم تقديم التجريد عندما يظهر متغير سلوك ثانٍ، وليس استباقيًا.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا