المفاهيم الخاطئة الشائعة حول OOP

مرحبا يا هبر!

اليوم ، تنتظرك نشرة مترجمة ، إلى حد ما ، تعكس عمليات البحث المتعلقة بالكتب الجديدة على OOP و FI. يرجى المشاركة في التصويت.



هل نموذج OOP ميت؟ هل يمكن القول أن البرمجة الوظيفية هي المستقبل؟ يبدو أن العديد من المقالات تكتب عن هذا. أنا أميل إلى الاختلاف مع وجهة النظر هذه. لنتكلم!

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

في وقت سابق ، كتبت أن OOP و FI لا تتناقض مع بعضها البعض. علاوة على ذلك ، تمكنت من الجمع بينهما بنجاح كبير.

لماذا يواجه مؤلفو هذه المقالات الكثير من المشكلات مع OOP ، ولماذا يبدو AF لديهم بديلاً واضحًا؟

كيفية تدريس OOP


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

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

ميراث


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

 class BlogController extends FrameworkAbstractController { } 

من المفترض أنه بهذه الطريقة سيكون من الأسهل عليك إجراء مكالمات مثل this.renderTemplate(...) ، حيث يتم توريث هذه الأساليب من فئة FrameworkAbstractController .

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

لا يوجد شيء للتشويش عليه. وإذا اخترنا النهج مع تكوين وحقن التبعية ، فإنه قد تحول مثل هذا:

 class BlogController { public BlogController ( TemplateRenderer templateRenderer ) { } } 

كما ترى ، لم تعد تعتمد على بعض FrameworkAbstractController الضبابية ، ولكنك تعتمد على شيء محدد جيدًا وضيق جدًا ، TemplateRenderer . في الواقع ، لا يرث BlogController من أي وحدة تحكم أخرى ، لأنه لا يرث أي سلوك.

التغليف


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

هذه الفرصة ، مرة أخرى ، تسمح بالاستخدام والإساءة. المثال الرئيسي للإساءة في هذه الحالة هو حالة تسرب.

وبشكل نسبي ، افترض أن الفئة List<> تحتوي على قائمة بالعناصر ، ويمكن تغيير هذه القائمة. لنقم بإنشاء فصل لمعالجة سلة الطلبات كما يلي:

 class ShoppingCart { private List<ShoppingCartItem> items; public List<ShoppingCartItem> getItems() { return this.items; } } 

هنا ، في معظم اللغات الموجهة نحو OOP ، سيحدث ما يلي: سيتم إرجاع متغير العناصر حسب المرجع. لذلك ، كذلك يمكننا القيام بذلك:

 shoppingCart.getItems().clear(); 

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

في هذا المثال بالذات ، قد يستخدم مؤلف فئة ShoppingCart الثبات للتحايل على المشكلة والتأكد من عدم انتهاك مبدأ التغليف.

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

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

التجريد


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

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

تعدد الأشكال


أخيرًا ، دعنا نتحدث عن تعدد الأشكال. يقترح أن فئة واحدة يمكن أن تنفذ العديد من السلوكيات. مثال سيء على الكتب المدرسية هو كتابة هذا Square مع تعدد الأشكال ويمكن أن يكون إما Rectangle أو Parallelogram . كما سبق أن أشرت أعلاه ، فإن هذا الأمر في OOP مستحيل بالتأكيد ، لأن سلوكيات هذه الكيانات مختلفة.

عند الحديث عن تعدد الأشكال ، ينبغي للمرء أن يأخذ في الاعتبار السلوكيات ، وليس الكود . ومن الأمثلة الجيدة على ذلك فئة Soldier في لعبة الكمبيوتر. يمكنه تنفيذ كل من السلوك Movable (الموقف: يمكنه التحرك) وسلوك Enemy (الموقف: يطلق النار عليك). في المقابل ، يمكن لفئة GunEmplacement فقط تنفيذ سلوك Enemy .

لذلك ، إذا كتبت Square implements Rectangle, Parallelogram ، فإن هذا البيان لا يصبح صحيحًا. يجب أن تعمل التجريدات الخاصة بك وفقًا لمنطق العمل. يجب أن تفكر أكثر في السلوك أكثر من الكود.

لماذا FP ليس رصاصة فضية


لذا ، عندما كررنا المبادئ الأساسية الأربعة لبرنامج OOP ، دعونا نفكر في ما هي ميزة البرمجة الوظيفية ، ولماذا لا نستخدمها لحل جميع المشاكل في شفرتك؟

من وجهة نظر العديد من أتباع FP ، والطبقات هي sacrilege ، وينبغي تقديم الكود في شكل وظائف . اعتمادًا على اللغة ، يمكن نقل البيانات من وظيفة إلى أخرى باستخدام أنواع بدائية ، أو في شكل مجموعة بيانات منظمة أو أخرى (صفائف ، قواميس ، إلخ).

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

يفصل هذا النهج البيانات عن الوظيفية - للوهلة الأولى ، يختلف FP بشكل جذري عن OOP. تؤكد FP أنه بهذه الطريقة ، تظل الشفرة بسيطة. تريد أن تفعل شيئًا ما ، اكتب وظيفة لهذا الغرض - هذا كل شيء.

تبدأ المشاكل عندما يجب أن تعتمد بعض الوظائف على وظائف أخرى. عندما تستدعي الدالة A الوظيفة B ، وتدعو الدالة B وظائف أخرى من خمس إلى ست وظائف ، وفي النهاية توجد وظيفة ملء الصفر يمكن أن تنهار - هذا هو المكان الذي لن تحسد عليه.

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

OOP أو FP؟


OOP و FI هي الأدوات . في النهاية ، لا يهم نموذج البرمجة الذي تستخدمه. تتعلق المشكلات الموضحة في معظم المقالات حول هذا الموضوع بتنظيم التعليمات البرمجية.

في رأيي ، البنية التحتية للتطبيق أكثر أهمية بكثير. ما هي وحدات في ذلك؟ كيف يتبادلون المعلومات مع بعضهم البعض؟ ما هي هياكل البيانات الأكثر شيوعا معك؟ كيف يتم توثيقها؟ ما الأشياء الأكثر أهمية من حيث منطق الأعمال؟

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

Source: https://habr.com/ru/post/ar469135/


All Articles