لماذا لا يعمل الميراث بالطريقة التي أعتقد أنها يجب أن تعمل؟

StackOverflow https://stackoverflow.com/questions/46933

سؤال

أواجه بعض مشكلات الميراث حيث أن لدي مجموعة من الفئات المجردة المترابطة التي يجب تجاوزها جميعًا معًا لإنشاء تطبيق العميل.من الناحية المثالية أود أن أفعل شيئًا مثل ما يلي:

abstract class Animal
{
  public Leg GetLeg() {...}
}

abstract class Leg { }

class Dog : Animal
{
  public override DogLeg Leg() {...}
}

class DogLeg : Leg { }

سيسمح هذا لأي شخص يستخدم فئة Dog بالحصول على DogLegs تلقائيًا وأي شخص يستخدم فئة Animal للحصول على الأرجل.المشكلة هي أن الوظيفة التي تم تجاوزها يجب أن يكون لها نفس نوع الفئة الأساسية لذلك لن يتم تجميعها.لا أرى سببًا لعدم القيام بذلك، نظرًا لأن DogLeg قابل للتحويل ضمنيًا إلى Leg.أعلم أن هناك الكثير من الطرق للتغلب على هذا الأمر، لكنني أشعر بالفضول أكثر بسبب عدم إمكانية/تنفيذ ذلك في C#.

يحرر:لقد قمت بتعديل هذا إلى حد ما، لأنني في الواقع أستخدم الخصائص بدلاً من الوظائف في الكود الخاص بي.

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

هل كانت مفيدة؟

المحلول

الإجابة المختصرة هي أن GetLeg ثابت في نوع الإرجاع الخاص به.الجواب الطويل يمكن العثور عليه هنا: التباين والتباين

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

نصائح أخرى

من الواضح ، ستحتاج إلى ممثلين إذا كنت تعمل على Dogleg مكسورة.

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

يتغير:

class Dog : Animal
{
  public override DogLeg GetLeg() {...}
}

ل:

class Dog : Animal
{
  public override Leg GetLeg() {...}
}

لا تفعل هذا:

 if(a instanceof Dog){
       DogLeg dl = (DogLeg)a.GetLeg();

إنه يهزم الغرض من البرمجة إلى النوع المجرد.

السبب وراء إخفاء DogLeg هو أن الدالة GetLeg في الفئة المجردة ترجع ملف Abstract Leg.إذا كنت تتجاوز GetLeg، فيجب عليك إرجاع الساق.هذا هو الهدف من وجود طريقة في فئة مجردة.لنشر هذه الطريقة بين أطفالها.إذا كنت تريد أن يعرف مستخدمو Dog عن DogLegs، فقم بإجراء طريقة تسمى GetDogLeg وقم بإرجاع DogLeg.

إذا كان بإمكانك أن تفعل ما يريده السائل، فسيحتاج كل مستخدم لـ Animal إلى معرفة جميع الحيوانات.

إنها رغبة صحيحة تمامًا في أن يكون للتوقيع طريقة متجاوزة نوع إرجاع يمثل نوعًا فرعيًا من نوع الإرجاع في الطريقة المتجاوزة (اوف).بعد كل شيء، فهي متوافقة مع نوع وقت التشغيل.

لكن C# لا تدعم حتى الآن "أنواع الإرجاع المتغيرة" في الطرق التي تم تجاوزها (على عكس C++ [1998] وJava [2004]).

ستحتاج إلى العمل والتكيف في المستقبل المنظور، كما ذكر إريك ليبرت مدونته[19 يونيو 2008]:

ويسمى هذا النوع من التباين "التباين المشترك من نوع الإرجاع".

ليس لدينا أي خطط لتنفيذ هذا النوع من التباين في لغة C#.

abstract class Animal
{
  public virtual Leg GetLeg ()
}

abstract class Leg { }

class Dog : Animal
{
  public override Leg GetLeg () { return new DogLeg(); }
}

class DogLeg : Leg { void Hump(); }

افعل ذلك على هذا النحو، ثم يمكنك الاستفادة من التجريد في عميلك:

Leg myleg = myDog.GetLeg();

ثم إذا كنت بحاجة إلى ذلك، يمكنك إلقاءها:

if (myleg is DogLeg) { ((DogLeg)myLeg).Hump()); }

مفتعل تمامًا ، ولكن النقطة المهمة هي أنه يمكنك القيام بذلك:

foreach (Animal a in animals)
{
   a.GetLeg().SomeMethodThatIsOnAllLegs();
}

مع الاحتفاظ بالقدرة على الحصول على طريقة Hump خاصة على Doglegs.

يمكنك استخدام الأدوية العامة والواجهات لتنفيذ ذلك في C#:

abstract class Leg { }

interface IAnimal { Leg GetLeg(); }

abstract class Animal<TLeg> : IAnimal where TLeg : Leg
 { public abstract TLeg GetLeg();
   Leg IAnimal.GetLeg() { return this.GetLeg(); }
 }

class Dog : Animal<Dog.DogLeg>
 { public class DogLeg : Leg { }
   public override DogLeg GetLeg() { return new DogLeg();}
 } 

يجب أن يقوم GetLeg() بإرجاع Leg ليكون تجاوزًا.ومع ذلك، لا يزال بإمكان فئة Dog الخاصة بك إرجاع كائنات DogLeg نظرًا لأنها فئة فرعية من Leg.يمكن للعملاء بعد ذلك أن يلقيوا عليها ويعملوا عليها على أنها كلاب.

public class ClientObj{
    public void doStuff(){
    Animal a=getAnimal();
    if(a is Dog){
       DogLeg dl = (DogLeg)a.GetLeg();
    }
  }
}

تم وصف المفهوم الذي يسبب لك المشاكل في http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)

لا يعني ذلك أن هذا كثير الاستخدام، ولكن ربما يكون من المثير للاهتمام ملاحظة أن Java تدعم العوائد المتغيرة، وبالتالي فإن هذا سيعمل تمامًا كما كنت تأمل.إلا أنه من الواضح أن Java ليس لديها خصائص؛)

ربما يكون من الأسهل رؤية المشكلة بمثال:

Animal dog = new Dog();
dog.SetLeg(new CatLeg());

الآن يجب أن يتم تجميع ذلك إذا كنت كلبًا، ولكن ربما لا نريد مثل هذا المتحول.

هناك مشكلة ذات صلة وهي هل يجب أن يكون Dog[] حيوانًا[]، أو IList<Dog> IList<Animal>؟

يحتوي C# على تطبيقات واجهة واضحة لمعالجة هذه المشكلة فقط:

abstract class Leg { }
class DogLeg : Leg { }

interface IAnimal
{
    Leg GetLeg();
}

class Dog : IAnimal
{
    public override DogLeg GetLeg() { /* */ }

    Leg IAnimal.GetLeg() { return GetLeg(); }
}

إذا كان لديك كلبًا من خلال مرجع من النوع Dog، فسيؤدي استدعاء GetLeg() إلى إرجاع DogLeg.إذا كان لديك نفس الكائن، ولكن المرجع من النوع IAnimal، فسوف يُرجع ساقًا.

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

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

يمكنك أيضًا إرجاع واجهة ILeg التي يطبقها كل من Leg و/أو DogLeg.

الشيء المهم الذي يجب تذكره هو أنه يمكنك استخدام نوع مشتق في كل مكان تستخدم فيه النوع الأساسي (يمكنك تمرير Dog إلى أي طريقة/خاصية/حقل/متغير يتوقع Animal)

لنأخذ هذه الوظيفة:

public void AddLeg(Animal a)
{
   a.Leg = new Leg();
}

دالة صالحة تمامًا، فلنستدعي الآن الدالة هكذا:

AddLeg(new Dog());

إذا لم تكن الخاصية Dog.Leg من النوع Leg، فستحتوي الدالة AddLeg فجأة على خطأ ولا يمكن تجميعها.

@ لوقا

أعتقد أنك ربما سوء فهم الميراث.سوف يقوم Dog.GetLeg() بإرجاع كائن DogLeg.

public class Dog{
    public Leg GetLeg(){
         DogLeg dl = new DogLeg(super.GetLeg());
         //set dogleg specific properties
    }
}


    Animal a = getDog();
    Leg l = a.GetLeg();
    l.kick();

الطريقة الفعلية التي يتم استدعاؤها ستكون Dog.GetLeg();و DogLeg.Kick() (أفترض وجود طريقة Leg.kick()) هناك، ونوع الإرجاع المعلن وهو DogLeg غير ضروري، لأن هذا هو ما تم إرجاعه، حتى لو كان نوع الإرجاع لـ Dog.GetLeg() هو رجل.

يمكنك تحقيق ما تريد باستخدام عام مع قيد مناسب، مثل ما يلي:

abstract class Animal<LegType> where LegType : Leg
{
    public abstract LegType GetLeg();
}

abstract class Leg { }

class Dog : Animal<DogLeg>
{
    public override DogLeg GetLeg()
    {
        return new DogLeg();
    }
}

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