بضائع

مفتوح / مغلق حسب مبدأ SOLID

يجب أن تكون كيانات البرامج (الفئات والوحدات والوظائف وما إلى ذلك) مفتوحة للتمديد ، ولكنها مغلقة للتحرير.

تصميم البرنامج: الوحدات النمطية والفئات والوظائف بطريقة أنه عندما تكون هناك حاجة إلى وظيفة جديدة ، يجب ألا نقوم بتعديل الكود الموجود ولكن بدلاً من ذلك يجب كتابة رمز جديد سيتم استخدامه بواسطة الكود الحالي. قد يبدو هذا غريبًا ، خاصة مع لغات مثل Java أو C أو C ++ أو C # حيث لا ينطبق فقط على كود المصدر نفسه ولكن أيضًا على الثنائي. نريد إنشاء ميزات جديدة بطرق لا تتطلب إعادة توزيع الثنائيات أو البرامج التنفيذية أو مكتبات DLL الحالية.
OCP في سياق SOLID

 

SRP و OCP التكميليان

لقد رأينا بالفعل مبدأ SRP الخاص بالمسؤولية الفردية والذي ينص على أن الوحدة يجب أن يكون لها سبب واحد فقط للتغيير. مبادئ OCP و SRP متكاملة. ستحترم الكود المصمم وفقًا لمبدأ SRP أيضًا مبادئ OCP. عندما يكون لدينا رمز له سبب واحد فقط للتغيير ، فإن تقديم ميزة جديدة سيخلق سببًا ثانويًا لهذا التغيير. لذلك سيتم اختراق كل من SRP و OCP. وبالمثل ، إذا كان لدينا رمز يجب أن يتغير فقط عندما تتغير وظيفته الرئيسية ويجب أن يظل دون تغيير عند إضافة وظيفة جديدة ، وبالتالي احترام OCP ، فسيحترم في الغالب SRP أيضًا.
هذا لا يعني أن SRP يؤدي دائمًا إلى OCP أو العكس ، ولكن في معظم الحالات إذا تم الالتزام بأحدهما ، يكون تحقيق الثاني أمرًا بسيطًا للغاية.

 

مثال على انتهاك مبدأ OCP

من وجهة نظر تقنية بحتة ، فإن مبدأ الفتح / الإغلاق بسيط للغاية. علاقة بسيطة بين فئتين ، مثل تلك أدناه ، تنتهك مبدأ OCP.

تستخدم فئة المستخدم فئة المنطق مباشرة. إذا احتجنا إلى تنفيذ فئة المنطق الثانية بطريقة تسمح لنا باستخدام كل من الفئة الحالية والجديدة ، فسيلزم تغيير فئة المنطق الحالية. يرتبط المستخدم مباشرة بتنفيذ المنطق ، فلا توجد طريقة لنا لتقديم منطق جديد دون التأثير على المنطق الحالي. وعندما نتحدث عن اللغات المكتوبة بشكل ثابت ، فمن المحتمل جدًا أن تتطلب فئة المستخدم تعديلات أيضًا. إذا تحدثنا عن اللغات المترجمة ، فمن المؤكد أن كل من الملف التنفيذي للمستخدم والملف التنفيذي المنطقي أو المكتبة الديناميكية سيتطلبان إعادة الترجمة والتسليم ، ويفضل تجنبهما قدر الإمكان.

بالإشارة إلى المخطط السابق ، يمكننا أن نستنتج أن أي فئة تستخدم فئة أخرى بشكل مباشر ، قد تؤدي إلى انتهاك مبدأ Open / Closed. 
لنفترض أننا نريد كتابة فصل دراسي قادر على توفير التقدم "بالنسبة المئوية" للملف الذي تم تنزيله ، من خلال تطبيقنا. سيكون لدينا فصلين رئيسيين ، تقدم وملف ، وأعتقد أننا نود استخدامهما على النحو التالي:

 

دالة testItCanGetTheProgressOfAFileAsAPercent () {
     ملف $ = ملف جديد () ؛
     ملف $-> الطول = 200 ؛
     $ file-> الإرسال = 100 ؛
     تقدم $ = تقدم جديد (ملف $) ؛
     $ this-> assertEquals (50، $ progress-> getAsPercent ()) ؛
}

في هذا الكود نحن تقدم المستخدمين. نريد الحصول على قيمة كنسبة مئوية ، بغض النظر عن حجم الملف الفعلي. نحن نستخدم الملف كمصدر للمعلومات. يبلغ طول الملف بالبايت وحقل يسمى الإرسال والذي يمثل مقدار البيانات المرسلة إلى أداة التنزيل. لا نهتم بكيفية تحديث هذه القيم في التطبيق. يمكننا أن نفترض أن هناك بعض المنطق السحري يفعل ذلك من أجلنا ، لذلك في الاختبار يمكننا تحديدها بوضوح.

 

ملف فئة {
     الطول بالدولار العام ؛
     تم إرسال دولار عام ؛
}

 

فئة الملف هي مجرد كائن بيانات بسيط يحتوي على الحقلين. بالطبع يجب أن يحتوي أيضًا على معلومات وسلوكيات أخرى ، مثل اسم الملف والمسار والمسار النسبي والدليل الحالي والنوع والأذونات وما إلى ذلك.

 

تقدم الفصل {

     ملف $ خاص ؛

     function __construct (ملف $ ملف) {
          $ this-> file = ملف $ ؛
     }

     دالة getAsPercent () {
          إرجاع $ this-> file-> sent * 100 / $ this-> file-> length؛
     }

}

التقدم هو ببساطة فئة تقبل ملفًا في منشئها. من أجل الوضوح ، حددنا نوع المتغير في معلمات الباني. هناك طريقة واحدة مفيدة في التقدم ، getAsPercent () ، والتي ستأخذ القيم المرسلة والطول من ملف وتحولها إلى نسبة مئوية. بسيط ويعمل.

يبدو أن هذا الرمز صحيح ، ولكنه ينتهك مبدأ Open / Closed.

لكن لماذا؟

وكيف؟

 

دعنا نحاول تغيير المتطلبات

سيحتاج كل تطبيق يتطور بمرور الوقت إلى ميزات جديدة. قد تكون الميزة الجديدة لتطبيقنا هي السماح بتدفق الموسيقى بدلاً من مجرد تنزيل الملفات. يتم تمثيل طول الملف بالبايت ، ومدة الموسيقى بالثواني. نريد أن نقدم شريط تقدم لمستمعينا ، لكن هل يمكننا إعادة استخدام الفصل المكتوب أعلاه؟

لا لانستطيع. تقدمنا ​​مرتبط بالملف. يمكنه فقط إدارة معلومات الملف ، على الرغم من أنه يمكن أيضًا تطبيقه على محتوى الموسيقى. ولكن للقيام بذلك ، يتعين علينا تعديله ، علينا أن نجعل التقدم يعرف الموسيقى والملفات. إذا امتثل تصميمنا لـ OCP ، فلن نحتاج إلى لمس ملف أو تقدم. يمكننا فقط إعادة استخدام التقدم الحالي وتطبيقه على الموسيقى.

 

النشرة الإخبارية
لا تفوّت أهم أخبار الابتكار. قم بالتسجيل لتلقيهم عن طريق البريد الإلكتروني.

حل ممكن

تتمتع اللغات المكتوبة ديناميكيًا بميزة إدارة أنواع الكائنات في وقت التشغيل. يسمح لنا ذلك بإزالة تلميح الكتابة من مُنشئ التقدم وسيستمر عمل الكود.

تقدم الفصل {

     ملف $ خاص ؛

     function __construct (ملف $) {
         $ this-> file = ملف $ ؛
     }

    دالة getAsPercent () {
         إرجاع $ this-> file-> sent * 100 / $ this-> file-> length؛
     }

}

يمكننا الآن إطلاق أي شيء في التقدم. وبأي شيء ، أعني حرفيًا أي شيء:

فئة الموسيقى {

الطول بالدولار العام ؛
تم إرسال دولار عام ؛

الفنان العام $؛
ألبوم $ عام؛
تاريخ الإصدار $ ؛

دالة getAlbumCoverFile () {
إرجاع "صور / أغلفة /". $ this-> الفنان. "/". $ this-> الألبوم. '.بي إن جي'؛
}
}

وسيعمل فصل الموسيقى مثل الفصل أعلاه بشكل مثالي. يمكننا اختباره بسهولة من خلال اختبار مشابه جدًا لـ File.
دالة testItCanGetTheProgressOfAMusicStreamAsAPercent () {
$ music = موسيقى جديدة ()؛
الموسيقى $-> الطول = 200 ؛
$ music-> الإرسال = 100 ؛

تقدم $ = تقدم جديد ($ music) ؛

$ this-> assertEquals (50، $ progress-> getAsPercent ()) ؛
}

لذلك ، يمكن استخدام أي محتوى قابل للقياس بشكل أساسي مع فئة التقدم. ربما يجب أن نضعه في الكود بتغيير اسم المتغير أيضًا:

تقدم الفصل {

محتوى $ خاص قابل للقياس ؛

function __construct ($ MeasurableContent) {
$ this-> محتوى قابل للقياس = محتوى قابل للقياس بالدولار ؛
}

دالة getAsPercent () {
إرجاع $ this-> MeasurableContent-> sent * 100 / $ this-> MeasurableContent-> length ؛
}

}

عندما حددنا الملف باعتباره تلميح الكتابة ، كنا متفائلين بشأن ما يمكن لفصلنا التعامل معه. لقد كان صريحًا وإذا حدث أي شيء آخر ، فسيخبرنا خطأ كبير.

Uفئة n التي تتجاوز طريقة الفئة الأساسية بحيث لا يتم احترام عقد الفئة الأساسية بواسطة الفئة المشتقة. 

لا نريد أن ينتهي بنا المطاف بمحاولة استدعاء الأساليب أو الوصول إلى الحقول على كائنات لا تتوافق مع عقدنا. عندما كان لدينا نوع تلميح ، تم تحديد العقد من خلاله. حقول وطرق فئة الملف. الآن بما أنه ليس لدينا أي شيء ، يمكننا إرسال أي شيء ، حتى سلسلة وسيؤدي ذلك إلى خطأ سيئ.

في حين أن النتيجة النهائية هي نفسها في كلتا الحالتين ، مما يعني أن الكود ينكسر ، فإن الأولى أنتجت رسالة لطيفة. هذا ، مع ذلك ، مظلمة للغاية. لا توجد طريقة لمعرفة ما هو المتغير - سلسلة في حالتنا - وما هي الخصائص التي تم البحث عنها ولم يتم العثور عليها. من الصعب تصحيح المشكلة وحلها. يجب على المبرمج فتح فئة التقدم وقراءتها وفهمها. العقد ، في هذه الحالة ، عندما لا تحدد بصراحة نوع تلميح ، هو defiصقل بسلوك التقدم. إنه عقد ضمني معروف فقط لـ Progress. في مثالنا ، هو كذلك definished عن طريق الوصول إلى الحقلين ، المرسلين والطول ، في طريقة getAsPercent (). في الحياة الواقعية ، يمكن أن يكون العقد الضمني معقدًا للغاية ويصعب اكتشافه بمجرد البحث عن بضع ثوانٍ في الفصل.

يوصى بهذا الحل فقط في حالة عدم إمكانية تنفيذ أي من النصائح الأخرى الواردة أدناه بسهولة أو إذا كانت ستُحدث تغييرات معمارية جادة لا تستدعي بذل الجهد.

Ercole Palmeri

النشرة الإخبارية
لا تفوّت أهم أخبار الابتكار. قم بالتسجيل لتلقيهم عن طريق البريد الإلكتروني.

المقالات الأخيرة

المستقبل هنا: كيف تُحدث صناعة الشحن ثورة في الاقتصاد العالمي

يعد القطاع البحري قوة اقتصادية عالمية حقيقية، وقد اتجه نحو سوق يبلغ حجمه 150 مليارًا...

1 مايو 2024

يوقع الناشرون وOpenAI اتفاقيات لتنظيم تدفق المعلومات التي تتم معالجتها بواسطة الذكاء الاصطناعي

أعلنت صحيفة فاينانشيال تايمز يوم الاثنين الماضي عن صفقة مع OpenAI. "فاينانشيال تايمز" ترخص صحافتها ذات المستوى العالمي...

أبريل 30 2024

المدفوعات عبر الإنترنت: إليك كيف تجعلك خدمات البث تدفع إلى الأبد

يدفع الملايين من الأشخاص مقابل خدمات البث، ويدفعون رسوم الاشتراك الشهرية. من الشائع أنك…

أبريل 29 2024

يتميز Veeam بالدعم الأكثر شمولاً لبرامج الفدية، بدءًا من الحماية وحتى الاستجابة والاسترداد

سوف تستمر شركة Coveware by Veeam في تقديم خدمات الاستجابة لحوادث الابتزاز السيبراني. ستوفر Coveware إمكانات الطب الشرعي والمعالجة...

أبريل 23 2024

اقرأ الابتكار بلغتك

النشرة الإخبارية
لا تفوّت أهم أخبار الابتكار. قم بالتسجيل لتلقيهم عن طريق البريد الإلكتروني.

تابعنا