2009-11-02 8 views
5

Lo siento por el título pobre, no se me ocurre una forma concisa de poner esto ..¿Es posible evitar el uso de verificación de tipos en este ejemplo?

Estoy pensando en tener una lista de objetos que serán todos de una interfaz específica. Cada uno de estos objetos puede implementar más interfaces, pero no hay garantía de qué objeto implementará qué. Sin embargo, en un solo ciclo, deseo poder llamar a los métodos de cualquier otro subtipo que puedan ser.

Ie, 3 interfaces:

public interface IAnimal { ... } 
public interface IEggLayer { public Egg layEgg(); } 
public interface IMammal { public void sweat(); } 

esto, entonces se almacena como

private List<IAnimal> animals= new ArrayList<IAnimal>(); 

así, instancias añadidos a la lista posiblemente también podría ser de tipo IEggLayer o IMammal, que tienen relación alguna métodos.

Mi instinto inicial sería luego hacer

for(IAnimal animal : animals) { 
    if(animal instanceof IEggLayer) { 
    egg = ((IEggLayer)animal).layEgg(); 
    } 
    if(animal instance of IMammal) { 
    ((IMammal)animal).sweat(); 
    } 
} 

Pero siempre han dicho que la comprobación de tipos es una señal de que el código realmente debe ser rediseñado.

Dado que podría ser posible para un solo objeto hacer ambos [ornitorrincos, por ejemplo], lo que significa que un solo doFunction() no sería adecuado aquí, ¿es posible evitar el uso de verificación de tipo en este caso? instancia donde la verificación de tipo se clasifica como aceptable?
¿Hay posiblemente un patrón de diseño atendido para esto?

Me disculpo por el ejemplo artificial, así ...
[Ignorar los errores de sintaxis, por favor - es sólo la intención de ser similar a Java pseudocódigo]

He añadido lvalue al uso egglayer, a muestre que a veces el tipo de devolución es importante

+0

este código parece ser java, ¿por qué la marca C#? –

+0

porque alguien más lo agregó. También puedo eliminar la etiqueta java que se ha agregado, ya que espero mantener este idioma independiente. – Farrell

Respuesta

1

Pero siempre me han dicho que la verificación de tipos es una señal de que el código realmente debe refactorizarse.

es una señal de que o jerarquía de clases o el código que lo usa puede necesidad de refactorizar o reestructuradas. Pero a menudo no habrá refactorización/reestructuración que evite el problema.

En este caso, cuando tiene métodos que se aplican solo a subtipos específicos, el refactor más prometedor sería tener listas separadas para los animales que son las capas de huevo y los animales que sudan.

Pero si no puede hacer eso, necesitará hacer algún tipo de comprobación. Incluso el isEggLayer()/isMammal() implica una verificación de tipo; p.ej.

if (x.isEggLayer()) { 
    ((IEggLayer) x).layEgg(); // type cast is required. 
} 

supongo que podría ocultar la verificación de tipos a través de un método asEggLayer(); p.ej.

public IEggLayer asEggLayer() { 
    return ((IEggLayer) this); 
} 

o

// Not recommended ... 
public IEggLayer asEggLayer() { 
    return (this instanceof IEggLayer) ? ((IEggLayer) this) : null; 
} 

Pero siempre hay una typecheck pasando, y la posibilidad de que se producirá un error. Además, todos estos intentos de ocultar la verificación de tipos implican agregar "conocimiento" de los subtipos a la interfaz de supertipo, lo que significa que debe modificarse a medida que se agregan nuevos subtipos.

+2

Por cierto, hay mamíferos que ponen huevos. –

+0

"En este caso, cuando tiene métodos que se aplican solo a subtipos específicos, el refactor más prometedor sería tener listas separadas para los animales que son capas de huevos y los animales que sudan". Creo que esta es la forma en que voy a tener que ir, las interfaces adicionales deberían poder hacerse mutuamente excluyentes de alguna manera, las interfaces tendrán el menor número de métodos posible ... – Farrell

+0

y sí, lo sé, por lo que escogí específicamente estas interfaces para el ejemplo [y mencioné el ornitorrinco en la pregunta] – Farrell

5

Claramente, su interfaz IAnimal (o alguna extensión de la misma) necesita un método callAllMethods que cada implementador de la interfaz pueda programar polimórficamente para esta tarea: ¡parece el único enfoque de sonido OO!

+0

Puedo ver de dónde viene esto, pero desafortunadamente no creo que funcione para mí, ya que en algunos casos puedo Necesito confiar en los objetos que el método llama return [de tipos específicos], y en otros casos no lo haré y no tiene sentido que el método tenga un tipo de devolución no nulo – Farrell

+0

Sin embargo, cada caso donde el procesamiento es type- el dependiente debe codificarse dentro del tipo (o clases anidadas del mismo), si desea afirmar que está haciendo Java OOP; entonces estos diferentes "algunos casos"/"otros casos" deben estar presentes como métodos en la interfaz. –

+2

Me gusta esto, sin embargo, podemos encontrar un nombre mejor. "liveAnotherDay()" o algo ... –

1

en C#, debería poder hacer esto de forma transparente.

foreach(IEggLayer egglayer in animals) { 
    egglayer.layEgg(); 
} 

foreach(IMammal mammal in animals) { 
    mammal.sweat(); 
} 
+0

Excepto que está usando java aparece, desde la sintaxis de bucle for. –

+0

desafortunadamente estoy trabajando en Java, lo que hasta donde puedo saber, no funciona - escribe mal coincidencia – Farrell

+0

(retenido a java) –

0

Por qué no tienen métodos añadidos a isAnimal:

public interface IAnimal { 
    bool isEggLayer(); 
    bool isMammal(); 
} 

Entonces puede recorrer y simplemente consultar esta booleano cada vez.

Actualización: Si esto estaba dibujando un animal, y luego tener una clase que está completamente cerrado es razonable, que acaba de llamar drawVehicle y se dibuja una corbeta, Cessna, motocicleta, lo que sea.

Pero, esto parece tener una arquitectura no-OOP, entonces si la arquitectura de la aplicación no puede cambiar entonces, dado que mi respuesta original no es bien recibida, entonces parecería que AOP sería la mejor opción .

Si pones una anotación en cada clase, puede tener

@IsEggLayer 
@IsMammal 
public class Platypus() implements EggLayer, Mammal { 
... 
} 

Esto entonces le permitiría crear aspectos que sacar todas las egglayers y hacer lo que hay que hacer operaciones.

También puede insertar en las interfaces con animales cualquier clase adicional para que esta idea funcione.

Tendré que pensar a dónde ir desde aquí, pero tengo la sensación de que esta puede ser la mejor solución, si no se puede rediseñar.

+0

principalmente porque esto significaría tener que cambiar IAnimal y todas sus implementaciones cada vez que agregue un tipo con nueva funcionalidad, en lugar de crear el nuevo ADT y solo necesita agregar la verificación en el ciclo. – Farrell

+0

No hay ninguna ventaja real al hacer la verificación de tipo fuera de línea - aún se rompen los principios básicos de OOP (aquí, la interfaz, así como el código de llamada, ambos deben conocer todos los posibles grupos de implementaciones, por lo que es aún peor). –

+0

No estoy seguro si no fue bien recibido es lo que pretendía con mi comentario. Más que la respuesta original estaba cambiando bastante una forma de comprobación de tipos para otra, que hasta donde puedo decir implicaría más mantenimiento cuando se introducen nuevas interfaces y funcionalidad – Farrell

1

Creo que la forma de pensar acerca de esta pregunta es: ¿Qué está haciendo el ciclo? El ciclo tiene un propósito y está tratando de hacer algo con esos objetos. Ese algo puede tener un método en la interfaz IAnimal, y las implementaciones pueden sudar o poner huevos según sea necesario.

En cuanto a su problema con el valor devuelto, devolverá nulo, nada que pueda hacer al respecto si comparte los métodos. No vale la pena lanzar dentro de un ciclo para evitar un retorno extra nulo; para satisfacer al compilador Puede, sin embargo, que sea más explícitos utilizando los genéricos:

public interface IAnimal<R> { 

     public R generalMethod(); 

    } 

    public interface IEggLayer extends IAnimal<Egg> { 

     public Egg generalMethod(); //not necessary, but the point is it works. 

    } 

    public interface IMammal extends IAnimal<Void> { 

     public Void generalMethod(); 

    } 

Desde su comentario en el que se preocupa por el tipo de retorno, se puede obtener el tipo de retorno y despachar a un método de fábrica, que examina el tipo y devuelve algo genérico que se subliga al tipo específico y actúa sobre eso.

0

Hay muchas maneras de hacerlo. Exactitud que es la más adecuada depende de su contexto. Voy a sugerir introducir una capa adicional de direccionamiento indirecto. Esto resuelve la mayoría de los problemas. Prefiero diseños que eviten la herencia múltiple de la interfaz (si fuera presidente de un lenguaje, lo prohibiría).

No creo que layEggsOrSweatOrDoBothIfYoureWeirdOrNoneIfYouCant() es un gran método para polute Animal con. Entonces, en lugar de agregar cada Animal directamente a animals, envuelva cada uno en un contenedor individual. Digo "envoltorio" como un nombre genérico: la operación aleatoria que estamos tratando de realizar no tiene ningún sentido.

private final List<AnimalWrapper> animals = 
    new ArrayList<AnimalWrapper>(); 

public void doStuff() { 
    for (AnimalWrapper animal : animals) { 
     animal.doStuff(); 
    } 
} 

Luego, necesitamos alguna forma de agregar los envoltorios. Algo como:

public void addPlatypus(final Platypus platypus) { 
    animals.add(new AnimalWrapper() { public void doYourStuff() { 
     platypus.sweat(); 
     platypus.layEgg(); 
    }}); 
} 

Si intenta escribir estas envolturas sin suficiente contexto, se meterá en problemas. Estos requieren que se seleccione el correcto en el sitio de llamadas. Podría hacerse por sobrecarga, pero eso tiene peligros.

/*** Poor context -> trouble ***/ 

public void addNormalMamal(final Mamal mamal) { 
    animals.add(new AnimalWrapper() { public void doYourStuff() { 
     mamal.sweat(); 
    }}); 
} 
public void addNormalEggLayer(final EggLayer eggLayer) { 
    animals.add(new AnimalWrapper() { public void doYourStuff() { 
     eggLayer.layEgg(); 
    }}); 
} 
public <T extends Mamal & EggLayer> void addMamalEggLayer(final T animal) { 
    animals.add(new AnimalWrapper() { public void doYourStuff() { 
     animal.sweat(); 
     animal.layEgg(); 
    }}); 
} 
Cuestiones relacionadas