2008-09-05 11 views
8

Tengo algunos problemas de herencia ya que tengo un grupo de clases abstractas interrelacionadas que deben ser reemplazadas todas juntas para crear una implementación de cliente. Idealmente me gustaría hacer algo como lo siguiente:¿Por qué la herencia no funciona como creo que debería funcionar?

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

abstract class Leg { } 

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

class DogLeg : Leg { } 

Esto permitiría que cualquier persona que utilice la clase de perro para obtener automáticamente curvas cerradas y cualquier persona que utilice la clase de animales para obtener las piernas. El problema es que la función reemplazada debe tener el mismo tipo que la clase base, por lo que no se compilará. Sin embargo, no veo por qué no debería hacerlo, ya que DogLeg es moldeable implícitamente para Leg. Sé que hay muchas formas de evitar esto, pero tengo más curiosidad sobre por qué esto no es posible/implementado en C#.

EDIT: He modificado esto un tanto, ya que estoy usando propiedades en lugar de funciones en mi código.

EDITAR: He cambiado de nuevo a las funciones, porque la respuesta sólo se aplica a esa situación (covarianza en el parámetro de valor de la función set de una propiedad no trabajo debería). Perdón por las fluctuaciones! Me doy cuenta de que hace que muchas de las respuestas parezcan irrelevantes.

Respuesta

15

La respuesta breve es que GetLeg es invariable en su tipo de devolución. La respuesta larga se puede encontrar aquí: Covariance and contravariance

Me gustaría agregar que, aunque la herencia suele ser la primera herramienta de abstracción que la mayoría de los desarrolladores sacan de su caja de herramientas, casi siempre es posible usar composición en su lugar.La composición es un poco más trabajo para el desarrollador de API, pero hace que la API sea más útil para sus consumidores.

+0

Su afirmación sobre la varianza no tiene sentido. En un subtipo: Covarianza = el tipo relacionado que se subtipo y Contravariancia = tipo relacionado que se superó. Esta pregunta representa un tipo de retorno covariante (subtipo utilizado en un subtipo), cuando C# solo permite un tipo de retorno invariante (el mismo). –

+0

Los artículos a los que se vinculó se refieren a la varianza de tipo genérico. La pregunta es sobre la covarianza del tipo de retorno. Declaro explícitamente en esos artículos que '* no * estoy hablando sobre la covarianza del tipo de retorno. –

+0

Eric Lippert: Pero esas cosas están intrínsecamente conectadas. Vea aquí: http://apocalisp.wordpress.com/2009/08/27/hostility-toward-subtyping/ – Apocalisp

2

GetLeg() debe devolver Leg para que sea una anulación. Sin embargo, su clase Dog puede devolver objetos DogLeg ya que son una clase secundaria de Leg. los clientes pueden lanzar y operar en ellos como doglegs.

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

El perro debe devolver una pierna no una DogLeg como tipo de devolución. La clase real puede ser un DogLeg, pero el punto es desacoplarse para que el usuario de Dog no tenga que saber sobre DogLegs, solo necesitan saber sobre las Piernas.

Cambio:

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

a:

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

no hacen esto:

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

en contra del propósito de la programación para el tipo abstracto.

La razón para ocultar DogLeg es porque la función GetLeg en la clase abstracta devuelve una pierna abstracta. Si está anulando el GetLeg debe devolver una pierna. Ese es el punto de tener un método en una clase abstracta. Para propagar ese método a su childern. Si desea que los usuarios del perro conozcan DogLegs, cree un método llamado GetDogLeg y devuelva DogLeg.

Si PODRÍA hacer lo que desea la pregunta, entonces cada usuario de Animal debería conocer TODOS los animales.

+0

Irrelevante. La pregunta se pregunta por qué un subtipo no puede anular un método con un método cuyo retorno sea un subtipo del retorno del destinatario. –

+1

Puedo entender el argumento de que el usuario de Animal solo debe saber sobre los objetos de Pierna, pero estoy menos convencido de que esto debería ocultarse a los usuarios de la clase Perro. – Luke

0

Derecho, entiendo que puedo simplemente lanzar, pero eso significa que el cliente tiene que saber que los perros tienen patas de perro. Lo que me pregunto es si hay razones técnicas por las que esto no es posible, dado que existe una conversión implícita.

0

@Brian Leahy Obviamente, si solo está trabajando en él como una pata, no hay necesidad o razón para lanzar. Pero si hay algún comportamiento específico de DogLeg o Dog, a veces hay razones por las que el yeso es necesario.

0

@Luke

Creo que su herencia quizá malentendido. Dog.GetLeg() devolverá un objeto 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(); 

el método real llamado será Dog.GetLeg(); y DogLeg.Kick() (supongo que existe un método Leg.kick()), el tipo de declaración declarada es DogLeg innecesario, porque eso es lo que devolvió, incluso si el tipo de devolución para Dog.GetLeg() es Pierna.

0

También podría devolver el ILeg de interfaz que implementen Leg y/o DogLeg.

3
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(); } 

hacerlo de esta manera, entonces se puede aprovechar la abstracción en su cliente:

Leg myleg = myDog.GetLeg(); 

A continuación, si es necesario, puede echarlo:

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

totalmente artificial, pero el punto es para que pueda hacer esto:

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

Al tiempo que conserva la posibilidad de tener un método Hump especial en Doglegs.

1

Tal vez sea más fácil ver el problema con un ejemplo:

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

Ahora que debería compilar si eres perro compilado, pero probablemente no quiero un mutante.

Un problema relacionado es el perro [] debe ser un animal [], o IList < Perro> un IList < Animal>?

0

Lo importante para recordar es que se puede utilizar un tipo derivado cada lugar se utiliza el tipo de base (que puede pasar perro a cualquier método/propiedad/campo/variable que espera Animal)

Tomemos esta función :

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

Una función perfectamente válido, ahora vamos a llamar a la función así:

AddLeg(new Dog()); 

Si la propiedad Dog.Leg no es del tipo de la pierna del AddLeg función s uddenly contiene un error y no se puede compilar.

2

No es que sea de mucha utilidad, pero tal vez sea interesante notar que Java admite retornos covariantes, por lo que esto funcionaría exactamente como esperaba.Excepto obviamente que Java no tiene propiedades;)

12

Claramente, necesitarás un yeso si estás operando en un DogLeg roto.

+1

jajaja juego agradable en palabras :) –

3

Puede utilizar los genéricos e interfaces para implementar que en 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();} 
} 
+0

Tenga en cuenta que esta solución de aspecto elegante puede dar lugar a complejidades debido a la falta de C# soporte genérico de varianza –

+0

¿qué tipo de complejidades? Esta solución no requiere ninguna variación. –

+0

¿Qué pasa si necesitamos un perro más complejo? Uno que tiene un DogTail, DogTeeth, DogEtc .. – Seiti

4

Es un deseo perfectamente válido tener la firma de un método de alteración temporal tiene un tipo de retorno que es un subtipo del tipo de retorno en el método reemplazado (phew). Después de todo, son compatibles con el tipo de tiempo de ejecución.

Pero C# aún no es compatible con los "tipos de retorno covariante" en los métodos reemplazados (a diferencia de C++ [1998] & Java [2004]).

Tendrá que trabajar alrededor y hacer que hacer para el futuro previsible, ya que Eric Lippert afirma en his blog [19 de junio de, de 2008]:

Ese tipo de varianza se denomina "tipo de covarianza retorno" .

no tenemos planes de implementar ese tipo de varianza en C#.

0

Puede lograr lo que desea mediante el uso de un genérico con una restricción apropiada, como la siguiente:

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 { } 
1

C# tiene implementaciones de interfaces explícitas para abordar precisamente esta cuestión:

abstract class Leg { } 
class DogLeg : Leg { } 

interface IAnimal 
{ 
    Leg GetLeg(); 
} 

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

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

Si tiene un perro a través de una referencia de tipo Perro, entonces al llamar a GetLeg() devolverá un DogLeg. Si tiene el mismo objeto, pero la referencia es de tipo IAnimal, devolverá una Leg.

Cuestiones relacionadas