2010-06-28 22 views
12

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

+0

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

Respuesta

22

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".

+0

+1. Gran desglose de qué usar y cuándo. – NotMe

+1

¿Eh? ¿Por qué el método prevalecería sobre el olor? – ladenedge

+5

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". –

1

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.

5

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.

+0

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. –

1

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.

0

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.

0

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.

1

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.

0

Prefiero clases base abstractas en escenarios siguientes:

  1. Una clase base no puede existir sin un sub class => la clase base es simplemente abstracto y puede' t ser instanciada.
  2. 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.
  3. 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.

Cuestiones relacionadas