Después de leer el libro más excelente "Patrones de diseño de Head First", comencé a hacer proselitismo a mis colegas sobre los beneficios de los patrones y principios de diseño. Mientras exalto las virtudes de mi patrón favorito - Patrón de estrategia - me hicieron una pregunta que me dio una pausa. La estrategia, por supuesto, usa herencia y composición y estaba en una de mis diatribas sobre "programar una interfaz (o supertipo) no una implementación", cuando un colega preguntó "¿por qué usar una clase base abstracta en lugar de una clase concreta?" .
Solo pude proponer "bien, fuerzan sus subclases para implementar métodos abstractos y les impiden crear instancias del ABC". Pero para ser honesto, la pregunta me sorprendió. ¿Son estos los únicos beneficios de usar una clase base abstracta sobre una clase concreta en la parte superior de mi jerarquía?Clase base abstracta vs. Clase concreta como SuperTipo
Respuesta
Si necesita implementar métodos específicos, utilice una interfaz. Si hay una lógica compartida que se puede extraer, use una clase base abstracta. Si el conjunto de funciones básicas está completo por sí mismo, puede usar una clase de concreación como base. Una clase base abstracta y una interfaz no se pueden crear instancias directamente, y esa es una de las ventajas. Si puede usar un tipo concreto, entonces necesita sobreescribir los métodos, y eso tiene un "olor a código".
+1. Gran desglose de qué usar y cuándo. – NotMe
¿Eh? ¿Por qué el método prevalecería sobre el olor? – ladenedge
Código con métodos virtuales es mucho más difícil de cambiar en el futuro sin romper un contrato de comportamiento implícito. Así que en .NET decidieron que hacer que un método sea invalidable tiene que ser una decisión consciente y debe considerarse cuidadosamente. Entonces, cualquier método invaluable sin instrucciones para el implementador es un "olor a código". –
Sí, aunque también podría usar una interfaz para forzar a una clase a implementar métodos específicos.
Otra razón para usar una clase abstracta en oposición a una clase concreta es que obviamente no se puede crear una instancia de una clase abstracta. A veces tampoco querrá que esto suceda, por lo que una clase abstracta es el camino a seguir.
Programa para la interfaz, no para la implementación tiene poco que ver con las clases abstractas y concretas. Recuerde el template method pattern? Clases, abstractas u concretas, son los detalles de implementación.
Y la razón para utilizar clases abstractas en lugar de clases concretas es que puede invocar métodos sin implementarlos, pero dejando que se implementen en subclases.
Programación de una interfaz es una cosa diferente - que es la definición de lo que hace su API, no cómo lo hace. Y esto se denota por interfaces.
Tenga en cuenta una diferencia clave: puede tener los métodos protected abstract
, lo que significa que se trata de detalles de implementación. Pero todos los métodos de interfaz son públicos: parte de la API.
Para agregar a eso, "Programa para una implementación" sería hacer cosas como usar refelection para acceder a los campos privados de una clase. –
En primer lugar, el patrón de estrategia casi nunca debe usarse en el moderno C#. Es principalmente para lenguajes como Java que no admiten punteros de función, delegados o funciones de primera clase. Lo verá en versiones anteriores de C# en interfaces como IComparer.
En cuanto a Abstract Base Class vs. Concrete Class, la respuesta en Java es siempre "¿Qué funciona mejor en esta situación?" Si sus estrategias pueden compartir código, entonces de ninguna manera déjelo.
Los patrones de diseño no son instrucciones sobre cómo hacer algo. Son formas de categorizar cosas que ya hemos hecho.
Si el cliente se basa en "contrato de comportamiento implícito [s]", está programado para una implementación y contra un comportamiento no garantizado. Anular el método al seguir el contrato solo expone errores en el cliente, no los causa.
OTOH, es menos probable que el error de asumir contratos que no están allí cause problemas si el método en cuestión no es virtual, es decir, anularlo no puede causar problemas porque no se puede anular. Solo si se cambia la implementación del método original (mientras se sigue obedeciendo el contrato) se puede romper el cliente.
La cuestión de si una clase base debe ser abstracta o concreta depende mi humilde opinión gran medida de si un objeto de clase base que implementa sólo los comportamientos que son comunes a todos los objetos de la clase sería útil. Considera un WaitHandle. Llamar "esperar" provocará que el código se bloquee hasta que se cumpla alguna condición, pero no hay una forma común de decirle a un objeto WaitHandle que se cumple su condición. Si se pudiera instanciar un "WaitHandle", en lugar de solo poder instanciar instancias de tipos derivados, dicho objeto tendría que esperar nunca o esperar siempre. El último comportamiento sería bastante inútil; el primero podría haber sido útil, pero podría lograrse casi tan bien con un evento manualResetEvent (creo que este último desperdicia algunos recursos, pero si se asigna estáticamente la pérdida total de recursos debería ser trivial).
En muchos casos, creo que mi preferencia sería utilizar referencias a una interfaz en lugar de a una clase base abstracta, pero proporcionará la interfaz de una clase base que proporciona un "modelo de aplicación". Entonces, cualquier lugar donde se use una referencia a un MyThing, uno proporcionaría una referencia a "iMyThing". Puede ser que el 99% (o incluso el 100%) de los objetos iMyThing sean realmente MyThing, pero si alguna vez alguien necesita tener un objeto iMyThing que herede algo diferente, uno podría hacerlo.
clases base abstractas se utilizan generalmente en escenarios donde el diseñador quiere obligar a un patrón arquitectónico donde ciertas tareas se llevarán a cabo de la misma manera por todas las clases, mientras que otros comportamientos dependen de las subclases. ejemplo:
public abstract class Animal{
public void digest(){
}
public abstract void sound(){
}
}
public class Dog extends Animal{
public void sound(){
System.out.println("bark");
}
}
patrón stratergy pide a los diseñadores a utilizar el comportamiento de composición de los casos en los que hay familias de alogirthms para un comportamiento.
Prefiero clases base abstractas en escenarios siguientes:
- Una clase base no puede existir sin un sub class => la clase base es simplemente abstracto y puede' t ser instanciada.
- Una clase base no puede tener aplicación plena o de hormigón de un método => Implementación de un método es la clase base es incompleta y sólo subclases puede proporcionar la implementación completa.
- clase base proporciona una plantilla para la implementación del método, pero todavía depende de la clase de hormigón para completar la implementación del método - Template_method_pattern
Un ejemplo sencillo para ilustrar los puntos
Shape
es el resumen más arriba y no puede existe sin forma concreta como Rectangle
. No se puede implementar un dibujo Shape
en la clase Shape
ya que las diferentes formas tienen fórmulas diferentes.La mejor opción para manejar escenario: dejar draw()
aplicación a subclases
abstract class Shape{
int x;
int y;
public Shape(int x,int y){
this.x = x;
this.y = y;
}
public abstract void draw();
}
class Rectangle extends Shape{
public Rectangle(int x,int y){
super(x,y);
}
public void draw(){
//Draw Rectangle using x and y : length * width
System.out.println("draw Rectangle with area:"+ (x * y));
}
}
class Triangle extends Shape{
public Triangle(int x,int y){
super(x,y);
}
public void draw(){
//Draw Triangle using x and y : base * height /2
System.out.println("draw Triangle with area:"+ (x * y)/2);
}
}
class Circle extends Shape{
public Circle(int x,int y){
super(x,y);
}
public void draw(){
//Draw Circle using x as radius (PI * radius * radius
System.out.println("draw Circle with area:"+ (3.14 * x * x));
}
}
public class AbstractBaseClass{
public static void main(String args[]){
Shape s = new Rectangle(5,10);
s.draw();
s = new Circle(5,10);
s.draw();
s = new Triangle(5,10);
s.draw();
}
}
de salida:
draw Rectangle with area:50
draw Circle with area:78.5
draw Triangle with area:25
Por encima de código abarca los puntos 1 y 2. Puede cambiar draw()
método como método de plantilla si la clase base tiene algún método de subclase de implementación e llamada para completar la función draw()
.
Ahora mismo ejemplo con el patrón Template Method:
abstract class Shape{
int x;
int y;
public Shape(int x,int y){
this.x = x;
this.y = y;
}
public abstract void draw();
// drawShape is template method
public void drawShape(){
System.out.println("Drawing shape from Base class begins");
draw();
System.out.println("Drawing shape from Base class ends");
}
}
class Rectangle extends Shape{
public Rectangle(int x,int y){
super(x,y);
}
public void draw(){
//Draw Rectangle using x and y : length * width
System.out.println("draw Rectangle with area:"+ (x * y));
}
}
class Triangle extends Shape{
public Triangle(int x,int y){
super(x,y);
}
public void draw(){
//Draw Triangle using x and y : base * height /2
System.out.println("draw Triangle with area:"+ (x * y)/2);
}
}
class Circle extends Shape{
public Circle(int x,int y){
super(x,y);
}
public void draw(){
//Draw Circle using x as radius (PI * radius * radius
System.out.println("draw Circle with area:"+ (3.14 * x * x));
}
}
public class AbstractBaseClass{
public static void main(String args[]){
Shape s = new Rectangle(5,10);
s.drawShape();
s = new Circle(5,10);
s.drawShape();
s = new Triangle(5,10);
s.drawShape();
}
}
de salida:
Drawing shape from Base class begins
draw Rectangle with area:50
Drawing shape from Base class ends
Drawing shape from Base class begins
draw Circle with area:78.5
Drawing shape from Base class ends
Drawing shape from Base class begins
draw Triangle with area:25
Drawing shape from Base class ends
Una vez que haya decidido que usted tiene que hacer como método abstract
, tiene dos opciones: o bien el usuario interface
o abstract
clase. Puede declarar sus métodos en interface
y definir la clase abstract
como clase implementando el interface
.
- 1. clase concreta de la clase abstracta
- 2. Derivando una clase abstracta de la clase concreta
- 3. ¿Por qué EnumSet se implementa como clase abstracta y EnumMap se implementa como clase concreta?
- 4. abstracción vs clase abstracta
- 5. cómo lograr la tabla por clase concreta cuando la clase base es abstracta en nhibernate fluido?
- 6. Base abstracta o clase auxiliar
- 7. Django: consultar una clase base abstracta
- 8. Tipos genéricos vs Clase abstracta/Interfaces
- 9. php clase abstracta extendiendo otra clase abstracta
- 10. Interface versus clase concreta
- 11. Singleton y clase base abstracta en C++
- 12. ¿En qué se diferencia la clase abstracta de la clase concreta?
- 13. ¿Cuál es la diferencia entre una clase concreta y una clase abstracta?
- 14. Python - Prueba de una clase base abstracta
- 15. Clase base vs Clase de utilidad
- 16. ¿Implementar clase abstracta como una clase local? pros y contras
- 17. Derivar clase abstracta de la clase no abstracta
- 18. Python 2.6, 3 malentendido de clase base abstracta
- 19. Clase base abstracta que hereda ICollection <T>
- 20. clase abstracta no implementa interfaz
- 21. delegado C# y clase abstracta
- 22. clase abstracta estática
- 23. (Programación a una interfaz v/s trabajar con clase concreta) cuando sólo hay una clase concreta
- 24. ¿Cómo puedo encontrar la "clase concreta" de una clase base del modelo django?
- 25. primavera, clase abstracta y anotaciones
- 26. clase abstracta Refactoring en C#
- 27. serialización de una clase abstracta
- 28. ¿Propiedad abstracta con getter público, define el setter privado en la clase concreta posible?
- 29. Servicio web ASMX que no serializa clase base abstracta
- 30. Sqlalchemy: evitar la herencia múltiple y tener clase base abstracta
En mi opinión SÍ. Pero creo que es una "característica" muy importante de un lenguaje forzar a alguien que hereda de esta clase a implementar un método. A veces necesita crear una clase general pero no puede implementar todas las características porque sería demasiado específico. – KroaX