¿Se considera que una interfaz está 'vacía' si se hereda de otras interfaces?

12

Las interfaces vacías generalmente se consideran una mala práctica, por lo que puedo decir, especialmente cuando el lenguaje admite los atributos como los atributos.

Sin embargo, ¿se considera que una interfaz está 'vacía' si se hereda de otras interfaces?

interface I1 { ... }
interface I2 { ... } //unrelated to I1

interface I3
    : I1, I2
{
    // empty body
}

Cualquier cosa que implemente I3 necesitará implementar I1 y I2 , y los objetos de diferentes clases que heredan I3 pueden usarse indistintamente (ver más abajo), así que es correcto llamar a I3 vacío ? Si es así, ¿cuál sería una mejor manera de estructurar esto?

// with I3 interface
class A : I3 { ... }
class B : I3 { ... }

class Test {
    void foo() {
        I3 something = new A();
        something = new B();
        something.SomeI1Function();
        something.SomeI2Function();
    }
}

// without I3 interface
class A : I1, I2 { ... }
class B : I1, I2 { ... }

class Test {
    void foo() {
        I1 something = new A();
        something = new B();
        something.SomeI1Function();
        something.SomeI2Function(); // we can't do this without casting first
    }
}
    
pregunta Kai 21.07.2015 - 17:10

3 respuestas

27
  

Cualquier cosa que implemente I3 necesitará implementar I1 e I2, y los objetos de diferentes clases que heredan I3 se pueden usar indistintamente (ver más abajo), ¿es correcto llamar a I3 vacío? Si es así, ¿cuál sería una mejor manera de estructurar esto?

"Agrupar" I1 y I2 en I3 ofrece un acceso directo (?) agradable, o algún tipo de sintaxis de azúcar para cualquier lugar donde desees implementar ambos.

El problema con este enfoque es que no se puede evitar que otros desarrolladores implementen explícitamente I1 y I2 lado a lado, pero no funciona en ambos sentidos, mientras que : I3 es equivalente a : I1, I2 , : I1, I2 no es equivalente a : I3 . Incluso si son funcionalmente idénticas, una clase que admita tanto I1 como I2 no se detectará como compatible con I3 .

Esto significa que estás ofreciendo dos formas de lograr lo mismo: "manual" y "dulce" (con tu sintaxis de azúcar). Y puede tener un mal impacto en la consistencia del código. Al ofrecer 2 formas diferentes de lograr lo mismo aquí, allí y en otro lugar, ya se obtienen 8 combinaciones posibles :)

void foo() {
    I1 something = new A();
    something = new B();
    something.SomeI1Function();
    something.SomeI2Function(); // we can't do this without casting first
}

Está bien, no puedes. Pero tal vez sea realmente una buena señal?

Si I1 y I2 implican responsabilidades diferentes (de lo contrario, no habría necesidad de separarlas en primer lugar), entonces tal vez foo() sea incorrecto para intentar que something cumpla dos responsabilidades diferentes en una. ¿ir?

En otras palabras, podría ser que foo() en sí mismo viole el principio de Responsabilidad Única, y el hecho de que requiera controles de tipo de lanzamiento y tiempo de ejecución para lograrlo es una señal de alerta que nos alarma sobre esto.

EDIT:

Si insistió en asegurarse de que foo tome algo que implemente I1 y I2 , podría pasarlos como parámetros separados:

void foo(I1 i1, I2 i2) 

Y podrían ser el mismo objeto:

foo(something, something);

¿Qué le importa a foo si son la misma instancia o no?

Pero si le importa (por ejemplo, porque no son apátridas), o simplemente piensa que esto se ve feo, se podrían usar genéricos en su lugar:

void Foo<T>(T something) where T: I1, I2

Ahora las restricciones genéricas se ocupan del efecto deseado sin contaminar el mundo exterior con nada. Esto es C # idiomático, basado en controles de tiempo de compilación, y se siente más correcto en general.

Veo a I3 : I2, I1 como un esfuerzo por emular union types en un lenguaje que no lo admite de la caja (C # o Java no, mientras que los tipos de unión existen en lenguajes funcionales).

Tratar de emular un constructo que no es realmente compatible con el lenguaje es a menudo torpe. De manera similar, en C # puede usar métodos de extensión e interfaces si desea emular mixins . ¿Posible? Sí. ¿Buena idea? No estoy seguro.

    
respondido por el Konrad Morawski 21.07.2015 - 17:28
4

Si hubiera una importancia particular en que una clase implementara ambas interfaces, entonces no veo nada de malo en crear una tercera interfaz que herede de ambas. Sin embargo, tendría cuidado de no caer en la trampa de la sobreingeniería. Una clase podría heredar de uno u otro, o ambos. A menos que mi programa tuviera muchas clases en estos tres casos, es demasiado poco crear una interfaz para ambas, y ciertamente no si esas dos interfaces no tuvieran relación entre sí (por favor, no las interfaces IComparableAndSerializable).

Le recuerdo que también puede crear una clase abstracta que herede de ambas interfaces, y puede usar esa clase abstracta en lugar de I3. Creo que sería incorrecto decir que no deberías hacerlo, pero al menos deberías tener una buena razón.

    
respondido por el Neil 21.07.2015 - 17:22
2
  

Las interfaces vacías generalmente se consideran una mala práctica

No estoy de acuerdo. Lea sobre interfaces de marcadores .

  

Mientras que una interfaz típica especifica funcionalidad (en forma de   declaraciones de método) que una clase implementadora debe soportar, un marcador   La interfaz no necesita hacerlo. La mera presencia de tal interfaz.   indica un comportamiento específico por parte de la clase implementadora.

    
respondido por el radarbob 22.07.2015 - 00:23

Lea otras preguntas en las etiquetas