Solía crear muchas clases / métodos abstractos. Entonces comencé a usar interfaces.
Ahora no estoy seguro si las interfaces no están volviendo obsoletas las clases abstractas.
¿Necesitas una clase completamente abstracta? Crea una interfaz en su lugar. ¿Necesita una clase abstracta con alguna implementación? Crea una interfaz, crea una clase. Herede la clase, implemente la interfaz. Un beneficio adicional es que algunas clases pueden no necesitar la clase padre, pero solo implementarán la interfaz.
Entonces, ¿son obsoletas las clases / métodos abstractos?
object-oriented
architecture
inheritance
interfaces
Boris Yankov
fuente
fuente
Respuestas:
No.
Las interfaces no pueden proporcionar una implementación predeterminada, las clases abstractas y el método sí pueden. Esto es especialmente útil para evitar la duplicación de código en muchos casos.
Esta también es una forma realmente agradable de reducir el acoplamiento secuencial. Sin clases / métodos abstractos, no puede implementar un patrón de método de plantilla. Le sugiero que mire este artículo de Wikipedia: http://en.wikipedia.org/wiki/Template_method_pattern
fuente
Existe un método abstracto para que pueda llamarlo desde su clase base pero implementarlo en una clase derivada. Entonces su clase base sabe:
Esa es una manera razonablemente buena de implementar un agujero en el patrón del medio sin que la clase derivada conozca las actividades de configuración y limpieza. Sin métodos abstractos, tendría que confiar en que la clase derivada implementara
DoTask
y recordara llamarbase.DoSetup()
ybase.DoCleanup()
todo el tiempo.Editar
Además, gracias a deadalnix por publicar un enlace al Patrón de método de plantilla , que es lo que he descrito anteriormente sin saber realmente el nombre. :)
fuente
public void DoTask(Action doWork)
Fruit
y su clase derivada esApple
, desea llamarmyApple.Eat()
, nomyApple.Eat((a) => howToEatApple(a))
. Además, no quiereApple
tener que llamarbase.Eat(() => this.howToEatMe())
. Creo que es más limpio simplemente anular un método abstracto.No, no son obsoletos.
De hecho, existe una diferencia oscura pero fundamental entre las clases / métodos abstractos y las interfaces.
si el conjunto de clases en el que uno de estos debe usarse tiene un comportamiento común que comparten (clases relacionadas, quiero decir), entonces vaya a clases / métodos abstractos.
Ejemplo: empleado, oficial, director: todas estas clases tienen CalculateSalary () en común, use clases base abstractas. CalculateSalary () puede implementarse de manera diferente, pero hay ciertas otras cosas como GetAttendance (), por ejemplo, que tiene una definición común en la clase base.
Si sus clases no tienen nada en común (clases no relacionadas, en el contexto elegido) entre ellas, pero tiene una acción que es muy diferente en la implementación, entonces vaya a Interfaz.
Ejemplo: vaca, banco, automóvil, clases no relacionadas con telesope pero Isortable puede estar allí para clasificarlas en una matriz.
Esta diferencia generalmente se ignora cuando se aborda desde una perspectiva polimórfica. Pero personalmente siento que hay situaciones en que una es más adecuada que la otra por la razón explicada anteriormente.
fuente
Además de las otras buenas respuestas, existe una diferencia fundamental entre las interfaces y las clases abstractas que nadie ha mencionado específicamente, a saber, que las interfaces son mucho menos confiables y, por lo tanto, imponen una carga de prueba mucho mayor que las clases abstractas. Por ejemplo, considere este código C #:
Tengo la garantía de que solo hay dos rutas de código que necesito probar. El autor de Frobbit puede confiar en el hecho de que el frobber es rojo o verde.
Si en cambio decimos:
Ahora no sé absolutamente nada sobre los efectos de esa llamada a Frob allí. Tengo que estar seguro de que todo el código en Frobbit es robusto contra cualquier posible implementación de IFrobber , incluso implementaciones de personas que son incompetentes (malas) o activamente hostiles hacia mí o mis usuarios (mucho peor).
Las clases abstractas te permiten evitar todos estos problemas; ¡usalos, usalos a ellos!
fuente
Lo dices tú mismo:
eso suena mucho trabajo en comparación con 'heredar la clase abstracta'. Puedes hacer el trabajo por ti mismo si te acercas al código desde una vista "purista", pero creo que ya tengo suficiente que hacer sin intentar agregar a mi carga de trabajo sin ningún beneficio práctico.
fuente
Como comenté en la publicación @deadnix: las implementaciones parciales son un antipatrón, a pesar de que el patrón de plantilla las formaliza.
Una solución limpia para este ejemplo de wikipedia del patrón de plantilla :
Esta solución es mejor, porque usa composición en lugar de herencia . El patrón de plantilla introduce una dependencia entre la implementación de las reglas de Monopoly y la implementación de cómo se deben ejecutar los juegos. Sin embargo, estas son dos responsabilidades completamente diferentes y no hay una buena razón para unirlas.
fuente
No. Incluso su alternativa propuesta incluye el uso de clases abstractas. Además, como no especificó el idioma, voy a seguir adelante y decir que el código genérico es la mejor opción que la herencia frágil de todos modos. Las clases abstractas tienen ventajas significativas sobre las interfaces.
fuente
Las clases abstractas no son interfaces. Son clases que no pueden ser instanciadas.
Pero entonces tendrías una clase inútil no abstracta. Se requieren métodos abstractos para completar el agujero de funcionalidad en la clase base.
Por ejemplo, dada esta clase
¿Cómo implementaría esta funcionalidad base en una clase que no tiene
Frob()
niIsFrobbingNeeded
?fuente
Soy un creador de servlet framework donde las clases abstractas juegan un papel esencial. Diría más, necesito métodos semi abstractos, cuando un método necesita ser anulado en un 50% de los casos y me gustaría ver una advertencia del compilador sobre que ese método no fue anulado. Resuelvo el problema agregando anotaciones. Volviendo a su pregunta, hay dos casos de uso diferentes de clases abstractas e interfaces, y hasta ahora nadie está obsoleto.
fuente
No creo que estén obsoletas por las interfaces, pero podrían estar obsoletas por el patrón de estrategia.
El uso principal de una clase abstracta es posponer una parte de la implementación; decir, "esta parte de la implementación de la clase se puede hacer diferente".
Desafortunadamente, una clase abstracta obliga al cliente a hacer esto a través de la herencia . Mientras que el patrón de estrategia le permitirá lograr el mismo resultado sin ninguna herencia. El cliente puede crear instancias de su clase en lugar de siempre definir las suyas, y la "implementación" (comportamiento) de la clase puede variar dinámicamente. El patrón de estrategia tiene la ventaja adicional de que el comportamiento se puede cambiar en tiempo de ejecución, no solo en tiempo de diseño, y también tiene un acoplamiento significativamente más débil entre los tipos involucrados.
fuente
En general, me he enfrentado a muchos más problemas de mantenimiento asociados con interfaces puras que ABC, incluso ABC utilizados con herencia múltiple. YMMV - no sé, tal vez nuestro equipo los usó de manera inadecuada.
Dicho esto, si usamos una analogía del mundo real, ¿cuánto uso hay para interfaces puras completamente desprovistas de funcionalidad y estado? Si uso USB como ejemplo, es una interfaz razonablemente estable (creo que ahora estamos en USB 3.2, pero también ha mantenido la compatibilidad con versiones anteriores).
Sin embargo, no es una interfaz sin estado. No carece de funcionalidad. Es más como una clase base abstracta que una interfaz pura. En realidad, está más cerca de una clase concreta con requisitos funcionales y estatales muy específicos, siendo la única abstracción lo que se conecta al puerto como la única parte sustituible.
De lo contrario, sería un "agujero" en su computadora con un factor de forma estandarizado y requisitos funcionales mucho más flexibles que no harían nada por sí solo hasta que cada fabricante inventara su propio hardware para hacer que ese agujero hiciera algo, en ese momento se convierte en un estándar mucho más débil y nada más que un "agujero" y una especificación de lo que debe hacer, pero no hay una disposición central sobre cómo hacerlo. Mientras tanto, podríamos terminar con 200 formas diferentes de hacerlo después de que todos los fabricantes de hardware intenten encontrar sus propias formas de vincular la funcionalidad y el estado a ese "agujero".
Y en ese momento podríamos tener ciertos fabricantes que introducen diferentes problemas sobre otros. Si necesitamos actualizar la especificación, podríamos tener 200 implementaciones concretas de puertos USB diferentes con formas totalmente diferentes de abordar la especificación que debe actualizarse y probarse. Algunos fabricantes pueden desarrollar implementaciones estándar de facto que comparten entre sí (su clase base analógica implementando esa interfaz), pero no todas. Algunas versiones pueden ser más lentas que otras. Algunos podrían tener un mejor rendimiento pero una latencia peor o viceversa. Algunos pueden usar más batería que otros. Algunos pueden fallar y no funcionar con todo el hardware que se supone que funciona con puertos USB. Algunos pueden requerir que se conecte un reactor nuclear para operar, lo que tiende a dar a sus usuarios envenenamiento por radiación.
Y eso es lo que he encontrado, personalmente, con interfaces puras. Puede haber algunos casos en los que tengan sentido, como simplemente modelar el factor de forma de una placa base contra una caja de la CPU. Las analogías de los factores de forma son, de hecho, prácticamente apátridas y carecen de funcionalidad, como ocurre con el "agujero" analógico. Pero a menudo considero que es un gran error que los equipos consideren que de alguna manera es superior en todos los casos, ni siquiera cerca.
Por el contrario, creo que ABC resolvería muchos más casos que las interfaces si esas son las dos opciones, a menos que su equipo sea tan gigantesco que sea realmente deseable tener el equivalente analógico anterior de 200 implementaciones USB competidoras en lugar de un estándar central para mantener. En un antiguo equipo en el que estaba, tuve que luchar mucho para aflojar el estándar de codificación para permitir ABC y herencia múltiple, y principalmente en respuesta a estos problemas de mantenimiento descritos anteriormente.
fuente