Dos interfaces con firmas idénticas

13

Estoy intentando modelar un juego de cartas en el que las cartas tienen dos conjuntos de características importantes:

El primero es un efecto. Estos son los cambios en el estado del juego que ocurren cuando juegas la carta. La interfaz para el efecto es la siguiente:

boolean isPlayable(Player p, GameState gs);
void play(Player p, GameState gs);

Y usted podría considerar que la carta es jugable solo si puede cubrir su costo y todos sus efectos son jugables. Al igual que:

// in Card class
boolean isPlayable(Player p, GameState gs) {
    if(p.resource < this.cost) return false;
    for(Effect e : this.effects) {
        if(!e.isPlayable(p,gs)) return false;
    }
    return true;
}

Bueno, hasta ahora, bastante simple.

El otro conjunto de características en la tarjeta son habilidades. Estas habilidades son cambios en el estado del juego que puedes activar a voluntad. Al crear la interfaz para estos, me di cuenta de que necesitaban un método para determinar si se pueden activar o no, y un método para implementar la activación. Termina siendo

boolean isActivatable(Player p, GameState gs);
void activate(Player p, GameState gs);

Y me doy cuenta de que, con la excepción de llamarlo "activar" en lugar de "jugar", Ability y Effect tienen exactamente la misma firma.

¿Es malo tener varias interfaces con una firma idéntica? ¿Debería simplemente usar uno y tener dos conjuntos de la misma interfaz? Como tal:

Set<Effect> effects;
Set<Effect> abilities;

Si es así, ¿qué pasos de refactorización debo tomar en el camino si no son idénticos (a medida que se lanzan más funciones), particularmente si son divergentes (es decir, ambos ganan algo al otro) ¿No debería, en contraposición a que solo una ganancia y la otra sea un subconjunto completo? Estoy particularmente preocupado de que combinarlos no será sostenible tan pronto como algo cambie.

La letra pequeña:

Reconozco que esta pregunta es generada por el desarrollo del juego, pero creo que es el tipo de problema que podría surgir fácilmente en el desarrollo no relacionado con el juego, especialmente cuando se trata de acomodar los modelos de negocios de múltiples clientes en una aplicación, como sucede. con casi todos los proyectos que he hecho con más de una influencia empresarial ... Además, los fragmentos de código utilizados son fragmentos de código Java, pero esto podría aplicarse fácilmente a una multitud de lenguajes orientados a objetos.

    
pregunta corsiKa 03.10.2012 - 18:30

4 respuestas

18

El hecho de que dos interfaces tengan el mismo contrato, no significa que sean la misma interfaz.

Principio de sustitución de Liskov afirma:

  

Sea q (x) una propiedad demostrable sobre objetos x de tipo T. Entonces q (y) debería ser demostrable para objetos y de tipo S donde S es un subtipo de T.

O, en otras palabras: todo lo que sea cierto para una interfaz o supertipo debería ser válido para todos sus subtipos.

Si entiendo su descripción correctamente, una habilidad no es un efecto y un efecto no es una habilidad. Si alguno de los dos cambia su contrato, es poco probable que el otro cambie con él. No veo ninguna buena razón para intentar vincularlos entre sí.

    
respondido por el pdr 03.10.2012 - 18:48
2

De Wikipedia : " la interfaz se usa a menudo para definir un tipo abstracto que no contiene datos, pero expone comportamientos definidos como métodos ". En mi opinión, una interfaz se utiliza para describir un comportamiento, por lo que si tiene diferentes comportamientos, tiene sentido tener interfaces diferentes. Al leer su pregunta, la impresión que tengo es que está hablando de diferentes comportamientos, por lo que las diferentes interfaces pueden ser el mejor enfoque.

Otro punto que tú mismo dijiste es que si uno de esos comportamientos cambia. Entonces, ¿qué sucede cuando solo tienes una interfaz?

    
respondido por el Joqus 03.10.2012 - 18:48
1

Si las reglas de su juego de cartas distinguen entre "efectos" y "habilidades", debe asegurarse de que sean interfaces diferentes. Esto evitará que uses uno de ellos accidentalmente donde se requiere el otro.

Dicho esto, si son extremadamente similares, puede tener sentido derivarlos de un ancestro común. Considere cuidadosamente: ¿tiene razones para creer que "efectos" y "habilidades" siempre necesariamente tendrán la misma interfaz? Si agrega un elemento a la interfaz effect , ¿esperaría que se agregue el mismo elemento a la interfaz ability ?

Si es así, entonces puede colocar dichos elementos en una interfaz común feature de la que se derivan ambos. Si no, no deberías intentar unificarlos, perderás tu tiempo moviendo cosas entre las interfaces base y derivadas. Sin embargo, dado que no tiene la intención de usar la interfaz base común para nada, pero "no se repita", puede que no haya tanta diferencia. Y, si te ciñes a esa intención, supongo que si tomas la decisión equivocada al comienzo, la refactorización para solucionarlo más tarde puede ser relativamente simple.

    
respondido por el comingstorm 03.10.2012 - 22:19
0

Lo que estás viendo es básicamente un artefacto de la expresividad limitada de los sistemas de tipos.

Teóricamente, si su sistema de tipos le permitiera especificar con precisión el comportamiento de esas dos interfaces, entonces sus firmas serían diferentes porque sus comportamientos son diferentes. En la práctica, la expresividad de los sistemas de tipos está limitada por limitaciones fundamentales, como el problema de la detención y el teorema de Rice, por lo que no se puede expresar todas las facetas del comportamiento de .

Ahora, diferentes tipos de sistemas tienen diferentes grados de expresividad, pero siempre habrá algo que no se puede expresar.

Por ejemplo: dos interfaces que tienen un comportamiento de error diferente pueden tener la misma firma en C #, pero no en Java (porque en Java las excepciones son parte de la firma). Dos interfaces cuyo comportamiento solo difiere en sus efectos secundarios pueden tener la misma firma en Java, pero tendrán firmas diferentes en Haskell.

Por lo tanto, siempre es posible que termines con la misma firma para diferentes comportamientos. Si crees que es importante poder distinguir entre esos dos comportamientos en más de un nivel nominal, entonces necesitas cambiar a un sistema de tipo expresivo más (o diferente).

    
respondido por el Jörg W Mittag 04.10.2012 - 03:59

Lea otras preguntas en las etiquetas