2010-09-15 17 views
7

Considere el siguiente problema:Método abstracto C# con implementación de clase base solamente?

Tiene una clase 'A' que sirve como una clase base para muchas otras clases similares, por ejemplo. una clase llamada B.

La clase A es útil en sí misma (y ya se usa en todas partes) y, por lo tanto, no es abstracta.

El quid de la cuestión es que ahora desea aplicar un nuevo protocolo que requiera que todas las clases heredadas de A (incluido A sí mismo) implementen un método 'func()'. Si una subclase olvida implementar func(), debería haber un error de compilación.

class A { 
    A func() { ret new A(...) } 
} 

class B : A { 
    A func() { ret new B(...) } 
} 

class C : A { 
    // Should give error : no func() 
} 

está claro el problema? No puedo hacer un resumen de A :: func() porque quiero seguir usando A como una clase concreta. No puedo hacerlo virtual, ya que eso provocaría que las subclases retrocedieran en la superclase (y no daría errores de compilación).

Lo único que se acerca a una solución es crear una nueva clase abstracta A * y heredar todos los tipos personalizados y reemplazar todos los usos actuales de A como una clase concreta con una nueva clase 'DefaultA' que hereda de UN*. Esto parece desordenado. Por favor dime que hay alguna otra forma de hacer esto?

+0

Su solución "desordenada" me parece la correcta. –

Respuesta

1

Si eso es lo que tiene que hacer, entonces sí.

En mi opinión, sin embargo, es posible que desee reconsiderar este requisito. A menudo he implementado marcos similares, donde forzar implementaciones en subclases era un truco ingenioso. Lo que encontré fue que como consumidor de de estos marcos, duplicaría el código o de manera predeterminada una implementación inane (como el método vacío). Ninguno de los cuales es ideal, ya que agregan ruido y ruido, y reducen el mantenimiento.

Además, si la aplicación de este patrón es de fabricación propia (es decir, su ejemplo tiene subclases que devuelven ejemplares de sí mismos), entonces puede intentar algo diferente, como un patrón de fábrica adecuado. Er, y por "intentar" me refiero a aprovechar una inversión de contenedor de control, como Castle Windsor, Ninject, Unity.

+0

¡Gracias por tus respuestas! Supongo que estoy siendo un poco paranoico :) Como soy un programador de C++ antiguo que comencé con C#, esperaba algo parecido a una función de plantilla que se rompería si se instanciara con el tipo incorrecto ... pero yo darse cuenta de que no es muy C# -ish – 4ZM

+0

Preferiría ver una subclase que anule la función abstracta ValidateStuff() con una implementación vacía, y un comentario de código que explique por qué no requiere ninguna validación. IMO, hace que el código sea más fácil de mantener y más útil como ejemplo para otros desarrolladores que crean subclases similares. Compare eso con una subclase que no anule la función ValidateStuff() virtual, y me deja preguntándome por qué. ¿No era necesario o el desarrollador simplemente se olvidó de implementarlo? Por esa razón, no creo que esas funciones vacías siempre sean simplemente "desorden". – mikemanne

10

Simplemente no hay forma de mantener A como un tipo concreto y forzar un error de compilación si los subtipos no anulan un método en particular.

+1

+1 Es cierto. Si desea aplicar en tiempo de ejecución la implementación del método, deberá aplicar una interfaz a TODAS las clases que requieran el (los) método (s) –

+0

. Uno siempre podría refactorizar la implementación para crear un 'ABase' que sea equivalente a' A' pero tenga 'func' como abstracto protegido, y luego hacer' sellado A: ABase' con una implementación predeterminada. – LBushkin

0

Esto es quizás incluso más complicado, pero es posible que pueda forzar B, C, D, etc. para implementar una interfaz que requiera func(). Luego asegúrese de que siempre se construyan a través de un método de fábrica. Así que algo vagamente como:

public class B: A, IFunc{ 
    private B(){} 
    public static B Create(){ return new B(); } 

} 
+0

Pero el objetivo es encontrar todas las clases que no se hayan modificado para implementar func(). Este método se perderá todas las clases que no se hayan modificado. Se requiere IFunc. Esto no resuelve el problema; simplemente lo mueve. –

1

No se puede crear una instancia de la clase A si es abstracta. Por lo tanto,

new A(); 

y

abstract A func(); // within the A class definition 

son contradictorios.

Si alejarse de abstracto, e ir virtual en lugar, puede obtener tiempo de ejecución (no en tiempo de compilación) comprobación:

// In the A class definition. 
protected virtual A func() 
{ 
    if (GetType() != typeof(A)) 
     throw // ... 
} 
1

Básicamente, se nos pide que "Hacer esta ruptura , pero no hagas que esto se rompa."Tal vez se ve el conflicto inherente.

Ésta es una de las razones por las clases base sólo deben utilizarse como clases base, y no se utiliza como ellos mismos.

2

Bueno, como usted dice, se podría crear una intermedio clase X abstracto que hereda de a y requieren que las clases derivadas heredan de X. X entonces declara un método abstracto protegido con la firma búsqueda de func y anular el func de la a a llamar a ese método siguiente es un ejemplo:.

class A { 
    public virtual A func() { ... } 
} 

abstract class X : A { 
    protected abstract A funcImpl(); 

    public override A func() { return funcImpl(); } 
} 

class B : X { /* must now implement funcImpl() to avoid compiler error */ } 

class C : X { /* must now implement funcImpl() to avoid compiler error */ } 

El principal problema con este enfoque es que requiere que todas las clases derivadas ahora hereden de X en lugar de A. Entonces, acaba de cambiar el problema de aplicación de un tipo a otro.

Un enfoque más limpio, en mi opinión, es refactorizar A en una clase base ABase y tienen func ser abstracta allí. Luego sellar A y requieren B y C heredar de ABase en lugar de A:

class ABase { 
    public abstract ABase func(); } 

sealed class A : ABase { 
    public override ABase func() { ... } 
} 

class B : ABase { 
    // forced to override func() 
    public override ABase func() { ... } 
} 

Usted tendría que sustituir todos los usos de A con ABase - pero hay algunas excelentes herramientas de refactorización en Visual Studio (y herramientas de terceros como ReSharper) que hacen esto sustancialmente menos doloroso de lo que alguna vez fue.

Cuestiones relacionadas