¿Tiene sentido definir una interfaz si ya tengo una clase abstracta?

12

Tengo una clase con alguna funcionalidad predeterminada / compartida. Yo uso abstract classpara ello:

public interface ITypeNameMapper
{
    string Map(TypeDefinition typeDefinition);
}

public abstract class TypeNameMapper : ITypeNameMapper
{
    public virtual string Map(TypeDefinition typeDefinition)
    {
        if (typeDefinition is ClassDefinition classDefinition)
        {
            return Map(classDefinition);
        }
        ...

        throw new ArgumentOutOfRangeException(nameof(typeDefinition));
    }

    protected abstract string Map(ClassDefinition classDefinition);
}

Como puede ver, también tengo la interfaz ITypeNameMapper. ¿Tiene sentido definir esta interfaz si ya tengo una clase abstracta TypeNameMappero si abstract classes suficiente?

TypeDefinition en este ejemplo mínimo también es abstracto.

Konrad
fuente
44
Solo para su información: en OOD, verificar los tipos en el código es una indicación de que su jerarquía de clases no es correcta: le está diciendo que cree clases derivadas del tipo que está comprobando. Entonces, en este caso, desea una interfaz ITypeMapper que especifique un método Map () y luego implemente esa interfaz en las clases TypeDefinition y ClassDefinition. De esa manera, su ClassAssembler simplemente itera a través de la lista de ITypeMappers, llamando a Map () en cada uno, y obtiene la cadena deseada de ambos tipos porque cada uno tiene su propia implementación.
Rodney P. Barbati
1
El uso de una clase base en lugar de una interfaz, a menos que sea absolutamente necesario, se llama "quemar la base". Si puede hacerse con una interfaz, hágalo con una interfaz. La clase abstracta se convierte en un extra útil. artima.com/intv/dotnet.html Específicamente, la comunicación remota requiere que usted derive de MarshalByRefObject, por lo que si desea ser remoto, no puede derivar de otra cosa.
Ben
@ RodneyP.Barbati no funcionará si quiero tener muchos mapeadores para el mismo tipo que puedo cambiar dependiendo de la situación
Konrad
1
@Ben quemando la base que es interesante. Gracias por la gema!
Konrad
@Konrad Eso es incorrecto: no hay nada que le impida implementar la interfaz llamando a otra implementación de la interfaz, por lo tanto, cada tipo puede tener una implementación enchufable que exponga a través de la implementación en el tipo.
Rodney P. Barbati

Respuestas:

31

Sí, porque C # no permite la herencia múltiple, excepto con interfaces.

Entonces, si tengo una clase que es TypeNameMapper y SomethingelseMapper, puedo hacer:

class MultiFunctionalClass : ITypeNameMapper, ISomethingelseMapper 
{
    private TypeNameMapper map1
    private SomethingelseMapper map2

    public string Map(TypeDefinition typeDefinition) { return map1.Map(typeDefintion);}

    public string Map(OtherDef otherDef) { return map2.Map(orderDef); }
}
Ewan
fuente
44
@Liath: La responsabilidad individual es en realidad un factor contribuyente de por qué se necesita la herencia. Considere tres rasgos posibles: ICanFly, ICanRun, ICanSwim. Necesitas crear 8 clases de criaturas, una para cada combinación de rasgos (YYY, YYN, YNY, ..., NNY, NNN). SRP indica que implementa cada comportamiento (volar, correr, nadar) una vez y luego implementarlos de manera reutilizable en las 8 criaturas. La composición sería aún mejor aquí, pero ignorando la composición por un segundo, ya debería ver la necesidad de heredar / implementar múltiples comportamientos reutilizables separados debido a SRP.
Flater
44
@ Konrad, eso es lo que quiero decir. Si escribe la interfaz y nunca la necesita, el costo es de 3 LoC. Si no escribe la interfaz y descubre que la necesita, el costo podría estar refactorizando toda su aplicación.
Ewan
3
(1) ¿Implementar interfaces es herencia? (2) La pregunta y la respuesta deben estar calificadas. "Depende." (3) La herencia múltiple necesita una etiqueta de advertencia. (4) Podría mostrarle K's de negligencia formal craptacular interface... implementaciones redundantes que deberían estar en la base, ¡que están evolucionando independientemente !, cambios lógicos condicionales que impulsan esta basura por falta de métodos de plantilla, encapsulación rota, abstracción inexistente. Mi favorito: clases de 1 método interface, cada una implementando su propio método 1 , cada una con una sola instancia. ... etc, etc, y etc.
radarbob
3
Hay un coeficiente invisible de fricción en el código. Lo siente cuando se ve obligado a leer interfaces superfluas. Luego se muestra calentándose como el transbordador espacial que se estrella contra la atmósfera a medida que una implementación de interfaz se eleva a la clase abstracta. Es la Zona Crepuscular y este es el transbordador Challenger. Al aceptar mi destino, miro el plasma furioso con una maravilla intrépida e indiferente. La fricción implacable desgarra la fachada de la perfección, depositando el destrozado casco en producción con un sonido atronador.
radarbob
44
@radarbob Poetic. ^ _ ^ Estoy de acuerdo; Si bien el costo de las interfaces es bajo, no es inexistente. No tanto en escribirlos, pero en una situación de depuración son 1-2 pasos más para llegar al comportamiento real, que puede sumar rápidamente cuando obtienes media docena de niveles de abstracción. En una biblioteca, generalmente vale la pena. Pero en una aplicación, la refactorización es mejor que la generalización prematura.
Erroresatz
1

Las interfaces y las clases abstractas tienen diferentes propósitos:

  • Las interfaces definen las API y pertenecen a los clientes, no a las implementaciones.
  • Si las clases comparten implementaciones, entonces puede beneficiarse de una clase abstracta .

En su ejemplo, interface ITypeNameMapperdefine las necesidades de los clientes y abstract class TypeNameMapperno agrega ningún valor.

Geoffrey Hale
fuente
2
Las clases abstractas también pertenecen a los clientes. No sé a qué te refieres con eso. Todo lo que es publicpertenece al cliente. TypeNameMapperestá agregando mucho valor porque evita que el implementador escriba alguna lógica común.
Konrad
2
Si una interfaz se presenta como interfaceo no solo es relevante, ya que C # prohíbe la herencia múltiple completa.
Deduplicador
0

Todo el concepto de interfaces fue creado para soportar una familia de clases que tienen una API compartida.

Esto dice explícitamente que cualquier uso de una interfaz implica que hay (o se espera que haya) más de una implementación de su especificación.

Los marcos DI han enturbiado las aguas aquí, ya que muchos de ellos requieren una interfaz a pesar de que solo habrá una implementación; para mí, esta es una sobrecarga irrazonable para lo que en la mayoría de los casos es solo un medio más complejo y más lento de llamar nuevo, pero Es lo que es.

La respuesta está en la pregunta misma. Si tiene una clase abstracta, se está preparando para la creación de más de una clase derivada con una API común. Por lo tanto, el uso de una interfaz está claramente indicado.

Rodney P. Barbati
fuente
2
"Si tiene una clase abstracta, ... el uso de una interfaz está claramente indicado". - Mi conclusión es la opuesta: si tiene una clase abstracta, úsela como si fuera una interfaz (si el DI no juega con ella, considere una implementación diferente). Solo cuando realmente no funcione, cree la interfaz; puede hacerlo en cualquier momento posterior.
maaartinus
1
Lo mismo, @maaartinus. Y ni siquiera "... como si fuera una interfaz". Los miembros públicos de una clase es una interfaz. También lo mismo para "puedes hacerlo en cualquier momento después"; esto va al corazón de los fundamentos del diseño 101.
radarbob
@maaartinus: si uno crea una interfaz desde el principio, pero usa referencias del tipo de clase abstracta en todo el lugar, la existencia de la interfaz no ayudará en nada. Sin embargo, si el código del cliente usa el tipo de interfaz en lugar del tipo de clase abstracta siempre que sea posible, eso facilitará mucho las cosas si es necesario producir una implementación que sea internamente muy diferente de la clase abstracta. Cambiar el código del cliente en ese punto para usar la interfaz será mucho más doloroso que diseñar el código del cliente para hacerlo desde el principio.
supercat
1
@supercat Podría estar de acuerdo asumiendo que estás escribiendo una biblioteca. Si se trata de una aplicación, no veo ningún problema para reemplazar todas las ocurrencias a la vez. Mi Eclipse puede hacerlo (Java, no C #), pero si no puede, es igual find ... -exec perl -pi -e ...y posiblemente corrige errores de compilación triviales. Mi punto es: la ventaja de no tener una cosa (actualmente) inútil es mucho mayor que los posibles ahorros futuros.
maaartinus
La primera oración sería correcta, si se marcara interfacecomo código (está hablando de C # - interfaces, no de una interfaz abstracta, como cualquier conjunto de clases / funciones / etc.), y la terminó "... pero aún así proscribe múltiples herencia." No hay necesidad de interfaces si tiene MI.
Deduplicador
0

Si queremos responder explícitamente a la pregunta, el autor dice: "¿Tiene sentido definir esta interfaz si ya tengo una clase abstracta TypeNameMapper o la clase abstracta es suficiente?"

La respuesta es sí y no: sí, debe crear la interfaz aunque ya tenga la clase base abstracta (porque no debería referirse a la clase base abstracta en ningún código de cliente), y no, porque no debería haber creado la clase base abstracta en ausencia de una interfaz.

Es evidente que no ha pensado lo suficiente en la API que está tratando de construir. Presente primero la API, luego proporcione una implementación parcial si lo desea. El hecho de que su clase base abstracta escriba código de verificación es suficiente para decirle que no es la abstracción correcta.

Como en la mayoría de las cosas en OOD, cuando está en el ritmo y tiene su modelo de objetos bien hecho, su código le informará sobre lo que viene después. Comience con una interfaz, implemente esa interfaz en las clases que la necesite. Si se encuentra escribiendo un código similar, extráigalo en una clase base abstracta: es la interfaz lo que es importante, la clase base abstracta es solo un auxiliar y puede haber más de una.

Rodney P. Barbati
fuente
-1

Esto es bastante difícil de responder sin conocer el resto de la aplicación.

Suponiendo que esté utilizando algún tipo de modelo DI y haya diseñado su código para que sea lo más extensible posible (para que pueda agregar nuevos TypeNameMapperfácilmente), diría que sí.

Razones para:

  • Efectivamente lo obtienes de forma gratuita, ya que la clase base implementa interfacelo que no necesitas preocuparte en ninguna implementación secundaria
  • La mayoría de los frameworks IoC y las bibliotecas de Mocking esperan interfaces. Si bien muchos de ellos trabajan con clases abstractas, no siempre es un hecho y creo firmemente en seguir el mismo camino que el otro 99% de los usuarios de una biblioteca siempre que sea posible.

Razones contra:

  • Estrictamente hablando (como has señalado), REALMENTE no necesitas
  • Si el class/ interfacecambia hay un poco de sobrecarga adicional

A fin de cuentas, diría que deberías crear el interface. Las razones en contra son bastante insignificantes, pero, aunque es posible usar resúmenes en burla e IoC, a menudo es mucho más fácil con las interfaces. Sin embargo, en última instancia, no criticaría el código de un colega si fuera por el otro lado.

Liath
fuente