سؤال

هذا السؤال لديه بالفعل إجابة هنا:

أنا مرتبك بعض الشيء بشأن التجاوز مقابل إخفاء طريقة في C#. كما سيكون موضع تقدير الاستخدامات العملية لكل منها ، بالإضافة إلى تفسير ل متى يمكن للمرء استخدام كل.

أنا في حيرة من أمري بشأن التجاوز - لماذا نتغلب؟ ما تعلمته حتى الآن هو أنه من خلال الإفراط في الإفراط في تقديم التنفيذ المرغوب فيه بطريقة فئة مشتقة ، دون تغيير التوقيع.

إذا لم أتجاوز طريقة الطبقة الفائقة وأجري تغييرات على الطريقة في الفصل الفرعي ، فهل سيجري ذلك تغييرات على طريقة الفئة الفائقة؟

أنا أيضًا مرتبك بشأن ما يلي - ماذا يظهر هذا؟

class A
{
    virtual m1()
    {
        console.writeline("Bye to all");
    }
}

class B : A
{
    override m1()
    {
        console.writeLine("Hi to all");
    }
}

class C
{
    A a = new A();
    B b = new B();
    a = b; (what is this)
    a.m1(); // what this will print and why?

    b = a; // what happens here?
}
هل كانت مفيدة؟

المحلول

انصح:

public class BaseClass
{
  public void WriteNum()
  {
    Console.WriteLine(12);
  }
  public virtual void WriteStr()
  {
    Console.WriteLine("abc");
  }
}

public class DerivedClass : BaseClass
{
  public new void WriteNum()
  {
    Console.WriteLine(42);
  }
  public override void WriteStr()
  {
    Console.WriteLine("xyz");
  }
}
/* ... */
BaseClass isReallyBase = new BaseClass();
BaseClass isReallyDerived = new DerivedClass();
DerivedClass isClearlyDerived = new DerivedClass();

isReallyBase.WriteNum(); // writes 12
isReallyBase.WriteStr(); // writes abc
isReallyDerived.WriteNum(); // writes 12
isReallyDerived.WriteStr(); // writes xyz
isClearlyDerived.WriteNum(); // writes 42
isClearlyDerived.writeStr(); // writes xyz

الإفراط هو طريقة OO الكلاسيكية التي يمكن أن يكون لها فئة مشتقة سلوكًا أكثر تحديدًا من فئة أساسية (في بعض اللغات التي لا تختارها سوى القيام بذلك). عندما يتم استدعاء طريقة افتراضية على كائن ، يتم استدعاء الإصدار الأكثر اشتقاقًا من الطريقة. وبالتالي على الرغم من أننا نتعامل مع isReallyDerived ك BaseClass ثم يتم تعريف الوظيفة في DerivedClass يستخدم.

الاختباء يعني أن لدينا طريقة مختلفة تماما. عندما ندعو WriteNum() على isReallyDerived ثم لا توجد طريقة لمعرفة أن هناك مجموعة مختلفة WriteNum() على DerivedClass لذلك لا يسمى. لا يمكن استدعاؤه إلا عندما نتعامل مع الكائن كما أ DerivedClass.

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

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

  2. التباين الفقير. النظر في Clone() طريقة على BaseClass هذا يعيد جديد BaseClass هذه نسخة من تلك التي تم إنشاؤها. في التجاوز DerivedClass هذا سيخلق ملف DerivedClass لكن إعادته كـ BaseClass, ، وهو ليس مفيدًا. ما يمكن أن نفعله هو أن يكون لديك حماية افتراضية CreateClone() تم تجاوز هذا. في BaseClass لدينا Clone() هذا يعيد نتيجة هذا - وكل شيء على ما يرام - في DerivedClass نخفي هذا مع جديد Clone() هذا يعود أ DerivedClass. الدعوة Clone() على BaseClass سيعود دائمًا BaseClass المرجع ، الذي سيكون BaseClass القيمة أو DerivedClass القيمة حسب الاقتضاء. الدعوة Clone() على DerivedClass سيعود أ DerivedClass القيمة ، وهو ما نريده في هذا السياق. هناك متغيرات أخرى لهذا المبدأ ، ومع ذلك تجدر الإشارة إلى أنها جميعها نادرة جدًا.

شيء مهم يجب ملاحظته في الحالة الثانية ، هو أننا استخدمنا الاختباء على وجه التحديد إزالة يفاجئ رمز الاتصال ، كشخص يستخدم DerivedClass قد تتوقع بشكل معقول Clone() لإرجاع أ DerivedClass. يتم الحفاظ على نتائج أي من الطرق التي يمكن استدعاؤها بما يتوافق مع بعضها البعض. معظم حالات إخفاء مخاطر إدخال المفاجآت ، وهذا هو السبب في أنها متعبئة بشكل عام. هذا ما يبرره بالضبط لأنه يحل المشكلة ذاتها التي يختبئها في كثير من الأحيان.

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

نصائح أخرى

الإفراط هو عندما تقدم جديدًا override تنفيذ طريقة في فئة سلي virtual.

الاختباء هو عندما تقدم تنفيذًا جديدًا لأسلوب ما في فئة سليل عندما تكون هذه الطريقة ليس محددة في الفئة الأساسية virtual, ، أو عندما لا يحدد تطبيقك الجديد override.

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

على سبيل المثال ، فكر في هذه الفئات:

public class BaseClass
{
  public virtual void Method1()  //Virtual method
  {
    Console.WriteLine("Running BaseClass Method1");
  }
  public void Method2()  //Not a virtual method
  {
    Console.WriteLine("Running BaseClass Method2");
  }
}
public class InheritedClass : BaseClass
{
  public override void Method1()  //Overriding the base virtual method.
  {
    Console.WriteLine("Running InheritedClass Method1");
  }
  public new void Method2()  //Can't override the base method; must 'new' it.
  {
    Console.WriteLine("Running InheritedClass Method2");
  }
}

دعنا نسميها هكذا ، مع مثيل للورث ، في إشارة مطابقة:

InheritedClass inherited = new InheritedClass();
inherited.Method1();
inherited.Method2();

هذا يعيد ما يجب أن تتوقعه ؛ تقول كلتا الطريقتين إنهما يديران إصدارات الموروثة.

تشغيل methodclass method1
تشغيل methodclass method2

ينشئ هذا الرمز مثيلًا لنفسه ، الموروثة ، ولكنه يخزنه في مرجع أساسي:

BaseClass baseRef = new InheritedClass();
baseRef.Method1();
baseRef.Method2();

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

تشغيل methodclass method1
تشغيل baseclass method2

عندما كتبت رمز الموروثة ، ربما كنت ترغب في جميع المكالمات Method2() لتشغيل الرمز الذي كتبته فيه. عادة ، سيكون هذا هو ما يعمل - على افتراض أنك تعمل مع أ virtual الطريقة التي تجاوزتها. ولكن لأنك تستخدم new/الطريقة المخفية ، يقوم باستدعاء الإصدار على المرجع الذي تستخدمه ، بدلاً من ذلك.


إذا كان هذا هو السلوك الذي حقا تريد, ، ومن بعد؛ ها أنت ذا. لكنني أقترح بشدة أنه إذا كان هذا هو ما تريده ، فقد تكون هناك مشكلة معمارية أكبر مع الكود.

الطريقة الغالبة هي تجاوز التنفيذ الافتراضي لطريقة فئة قاعدة في الفئة المشتقة.

طريقة إخفاء: يمكنك استخدام الكلمة الرئيسية "الجديدة" قبل طريقة افتراضية في فئة مشتقة

كما

class Foo  
{  
  public virtual void foo1()  
  {  

  }  
}  

class Bar:Foo  
{  
  public new virtual void foo1()  
  {   

  }  
}  

الآن إذا قمت بإنشاء BAR1 فئة أخرى مشتقة من BAR ، فيمكنك تجاوز FOO1 الذي يتم defind in bar.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top