ما هو التغاير والتباين في البرمجة؟ – CloudSavvy IT
التباين والتباين هما المصطلحان اللذان يصفان كيفية تعامل لغات البرمجة مع الأنواع الفرعية. يحدد الاختلاف في النوع ما إذا كان يمكن استخدام الأنواع الفرعية بالتبادل معها.
قبل تقديم أمثلة محددة ، يعتبر التباين مفهومًا مبهمًا على ما يبدو.دعونا نفكر في النوع الأساسي Animal
نوع فرعي Dog
.
interface Animal public function walk() : void; interface Dog extends Animal public function bark() : void;
يمكن لجميع “الحيوانات” المشي ، ولكن “الكلاب” فقط يمكنها النباح. الآن ، دعونا نفكر في ما يحدث عندما نستخدم هذا التسلسل الهرمي للكائن في تطبيقنا.
قم بتوصيل الواجهة معًا
بسبب كل Animal
يمكننا المشي ، يمكننا إنشاء واجهة عالمية لممارسة أي منها Animal
.
interface AnimalController public function exercise(Animal $Animal) : void;
هذه AnimalController
عند exercise()
طريقة سريعة Animal
واجهه المستخدم.
interface DogRepository public function getById(int $id) : Dog;
الآن لدينا ملف DogRepository
الطرق المضمونة لإرجاع طريقة ما Dog
.
إذا حاولنا مقارنة هذه القيمة بـ AnimalController
؟
$AnimalController -> exercise($DogRepository -> getById(1));
في اللغات التي تدعم المعلمات المتغيرة ، هذا مسموح به. AnimalController
يجب أن تحصل على واحدة Animal.
ما نمرره هو في الواقع Dog,
لكنها ما زالت مرضية Animal
اتفافية.
هذه العلاقة مهمة بشكل خاص عند توسيع الفصول الدراسية.قد نريد عام AnimalRepository
يمكنك البحث عن أي حيوان ليس لديه معلومات مفصلة عن أنواعه.
interface AnimalRepository public function getById(int $id) : Animal; interface DogRepository extends AnimalRepository public function getById(int $id) : Dog;
DogRepository
تعديل العقد AnimalRepository
-لأن المتصل سيحصل على واحدة Dog
بدلا من Animal
-لكنه لم يغيره بشكل جذري. إنه يشرح فقط نوع الإرجاع بمزيد من التفصيل.نوع Dog
مازال Animal.
النوع هو متغير ، لذلك DogRepository
تعريف مقبول.
انظر إلى العجز
دعونا ننظر الآن في المثال المعاكس.قد تحتاج واحدة DogController,
لقد غير هذا الطريقة التي يمارس بها “الكلب”.منطقيا ، هذا لا يزال من الممكن تمديده AnimalController
واجهه المستخدم.ومع ذلك ، في الواقع ، لا تسمح لك معظم اللغات بالتجاوز exercise()
بالطريقة الضرورية.
interface AnimalController public function exercise(Animal $Animal) : void; interface DogController extends AnimalController public function exercise(Dog $Dog) : void;
في هذا المثال DogController
مكلف exercise()
تقبل واحدة فقط Dog
.هذا يتعارض مع تعريف المنبع AnimalController
، السماح بمرور أي “حيوانات”. من أجل الوفاء بالعقد ، DogController
لذلك ، أي Animal
.
للوهلة الأولى ، يبدو هذا محيرًا وغير مفيد.عندما تستهدف AnimalController
:
function exerciseAnimal( AnimalController $AnimalController, AnimalRepository $AnimalRepository, int $id) : void $AnimalController -> exercise($AnimalRepository -> getById($id));
والقضية هي AnimalController
ربما AnimalController
أو أ DogController
—طريقتنا لا تعرف تطبيق الواجهة الذي تستخدمه. هذا يرجع إلى نفس قواعد التغاير التي كانت متاحة سابقًا.
مثل AnimalController
مايو اصبح ال DogController
، يوجد الآن خطأ جسيم في وقت التشغيل ينتظر من يكتشفه. AnimalRepository
يقوم دائمًا بإرجاع ملف Animal
، لذلك ، إذا $AnimalController
نعم نوع DogController
، سوف يتعطل التطبيق.هذه Animal
النوع غامض جدًا بحيث لا يمكن تمريره إليه DogController
exercise()
طريقة.
من الجدير بالذكر أن اللغات التي تدعم أسلوب التحميل الزائد ستقبل DogController
.يسمح لك التحميل الزائد بتحديد طرق متعددة بنفس الاسم ، بشرط أن يكون لها أسماء مختلفة التوقيع (لديهم معلمات مختلفة و / أو أنواع إرجاع). DogController
سيكون هناك اضافي exercise()
تقبل فقط طريقة “الكلب”. ومع ذلك ، فإنه يحتاج أيضًا إلى تنفيذ التوقيعات الأولية التي تقبل أي “حيوانات”.
تعامل مع التناقضات
يمكن القول أنه يمكن تلخيص نوع إرجاع الدالة على النحو الوارد أعلاه التغاير ويجب أن يكون نوع المعلمة المخالف. هذا يعني أن الوظيفة قد ترجع نوعًا أكثر تحديدًا من تعريف الواجهة. يمكنه أيضًا قبول أنواع أكثر تجريدًا كمعلمات (على الرغم من أن معظم لغات البرمجة الشائعة لا تطبقها).
عند استخدام الأدوية العامة والمجموعات ، غالبًا ما تواجه مشكلات تباين.في هذه الحالات ، تريد عادةً AnimalCollection
و أ DogCollection
.ينبغي DogCollection
تمديد AnimalCollection
؟
هذه الواجهات هي كما يلي:
interface AnimalCollection public function add(Animal $a) : void; public function getById(int $id) : Animal; interface DogCollection extends AnimalCollection public function add(Dog $d) : void; public function getById(int $id) : Dog;
النظرة الأولى getById()
و Dog
هو نوع فرعي من Animal
. الأنواع متغايرة ، ويسمح بأنواع الإرجاع المتغيرة. هذا مقبول.نلاحظ مشكلة التباين مرة أخرى add()
على الرغم من-DogCollection
يجب أن يسمح بأي Animal
لإرضاء AnimalCollection
اتفافية.
عادة ، يمكن حل هذه المشكلة بشكل أفضل بجعل المجموعة غير قابلة للتغيير. السماح فقط بإضافة عناصر جديدة إلى مُنشئ المجموعة.ثم يمكنك القضاء add()
في المجموع ، اصنع AnimalCollection
مرشح فعال DogCollection
يرث.
أشكال أخرى من الاختلاف
بالإضافة إلى التباين المشترك والتباين العكسي ، قد تواجه أيضًا المصطلحات التالية:
- المتغيرات المزدوجة: إذا تم تطبيق التغاير واللوغاريتم على علاقة النوع في نفس الوقت ، يكون نظام الكتابة ثنائي المتغير. يستخدم TypeScript قبل TypeScript 2.6 التباين الثنائي كمعامل.
- المتغيرات: إذا تم استخدام التباين المشترك أو التباين العكسي ، يكون النوع متغيرًا.
- ثابت: لا توجد متغيرات من أي نوع.
عادة ، سوف تستخدم أنواعًا متغيرة أو متباينة. فيما يتعلق بميراث الفئة ، إذا كان النوع B والنوع A متغيرين ، تمديد A. إذا كان النوع B هو أحد أسلاف B ، فإنه والنوع A يكونان قابلين للتحويل.
ختاما
التباين هو مفهوم يشرح القيود داخل نظام النوع. بشكل عام ، ما عليك سوى تذكر أن التباين المشترك مقبول في نوع الإرجاع ، ويتم استخدام التباين المشترك للمعلمات.
قاعدة التباين مشتق من مبدأ استبدال Liskov. هذا يعني أنه يجب أن تكون قادرًا على استبدال مثيل لفئة بمثيل من صنفها الفرعي دون تغيير أي خصائص للنظام الأوسع.هذا يعني أنه إذا امتد النوع B إلى النوع A ، فعندئذٍ A
يمكن استخدام الأمثلة التالية بدلاً من ذلك B
.
استخدام المثال أعلاه يعني أننا يجب أن نكون قادرين على الاستبدال Animal
مع Dog
، أو AnimalController
مع DogController
.هنا نرى مرة أخرى لماذا DogController
لا يمكن الكتابة exercise()
فقط نقبل الكلاب – لن نتمكن من استبدالها بعد الآن AnimalController
مع DogController
، لأن المستهلكين مرت حاليا Animal
الآن بحاجة إلى توفير ملف Dog
بدلا من. التباين والتغاير يطبقان LSP ويضمنان معايير سلوك متسقة.