2010-08-12 19 views
5

Situación:C# OO problema de diseño con anulación de métodos

Assembly 1 
________________________    ________________________ 
| Class A    |   | Class B    | 
|-----------------------|   |-----------------------| 
| Method someMethod  |---------->| Method otherMethod | 
|      |   |      | 
|_______________________|   |_______________________| 

Asamblea 1 es una aplicación que otros desarrolladores pueden utilizar. Les daremos solo el .dll para que podamos publicar actualizaciones desde la aplicación si no cambiamos la API. Los desarrolladores no pueden cambiar el marco en el ensamblaje 1

Los métodos son virtuales, por lo que los desarrolladores pueden sobrescribir los métodos para implementar su propia lógica si es necesario.

El problema es que un desarrollador no puede anular otherMethod de la clase B, , puede anularlo, pero la Clase A siempre llamará al método de la Clase B y no al método reemplazado.

Assembly 1 
________________________    ________________________ 
| Class A    |   | Class B    | 
|-----------------------|   |-----------------------| 
| Method someMethod  |----XX---->| Method otherMethod | 
|      |   |      | 
|_______________________|   |_______________________| 
       \         | 
       \         | 
        \         | 
Assembly 2   \         | 
        \    ________________|_______ 
        \    | Class ExtendedB  | 
         \    |-----------------------| 
         \____________>| Method otherMethod | 
            |      | 
            |_______________________| 

Asamblea 2 haves una referencia al ensamblaje 1

clase parcial no funciona porque tiene que ser el mismo conjunto y no funcionará durante 2

¿Hay patrones de diseño para este problema? ¿O hay alguna otra solución con reflejo o algo así?

EDITAR añadido un ejemplo de código:

/* ASSEMBLY 1 */ 

namespace Assembly1 
{ 
    public interface IAService 
    { 
     void TestMethod3(); 
     void TestMethod4(); 
    } 

    public interface IBService 
    { 
     void TestMethod1(); 
     void TestMethod2(); 
    } 

    public class AService : IAService 
    { 
     // Base implementation of AService 
     public virtual void TestMethod3() 
     { 
      //do something 
     } 
     public virtual void TestMethod4() 
     { 
      //do something 
     } 
    } 

    public class BService : IBService 
    { 
     // Base implementation of BService 
     public virtual void TestMethod1() 
     { 
      //do something 
     } 
     public virtual void TestMethod2() 
     { 
      //need to call AService implementation from assembly 2 
     } 
    } 
} 





/* ASSEMBLY 2 */ 
namespace Assembly2 
{ 
    public class NewAService : AService 
    { 
     public override void TestMethod3() 
     { 
      //default implementation which could be overridden 
      base.TestMethod3(); 
     } 

     public override void TestMethod4() 
     { 
      //default implementation which could be overridden 
      //An implementation of IBService Should be called 

      base.TestMethod4(); 
     } 
    } 
} 
+0

¿Cómo se llama al método en la clase B? ¿Se accede de forma estática? ¿El ensamblaje 2 (o cualquier otro ensamblaje) pasa un objeto de tipo ExtendedB a la clase A (inyección de dependencia)? – dbemerlin

+1

¿Podría proporcionar un fragmento de código? Los diagramas no describen el * uso * muy bien. – DevinB

+0

Gente, ¿por qué estamos sugiriendo la implementación de la interfaz antes de siquiera entender por qué la herencia no permite el polimorfismo? –

Respuesta

4

debe refactorizar

public interface IClassB 
{ 
    void SomeMethod(); 
} 
public Class A 
{ 
    private IClassB myInstanceB = new ClassB(); 

    public ClassA(){} 

    public ClassA(IClass B) 
    { 
     myInstanceB = B; 
    } 

    public void SomeMethod() 
    { 
     myInstanceB.SomeMethod(); 
    } 
} 

public ClassB : IClassB 
{ 
    public void SomeMethod() 
    { 
     // some wicked code here... 
    } 
} 

con esta refactorización hecho, los desarrolladores pueden utilizar la aplicación por defecto utilizando el constructor vacío. si necesitan alguna otra lógica que simplemente tienen que implementar la interfaz IClassB y simplemente pasarla en el otro constructor.

el uso en el montaje 2 sería algo como esto

public class NewFunctionalityClass : IClassB 
{ 
    public void SomeMethod() 
    { 
     //something else 
    } 
} 
public TestClass() 
{ 
    public void ShowMethod() 
    { 
     var defaultObject = new ClassA(); 
     defaultObject.SomeMethod(); // default implementation 

    var otherObject = new ClassA(new NewFunctionalityClass()); 
    otherObject.SomeMethod(); // here the new implementation will run 
    } 
} 
+0

No veo cómo esto ayuda. –

+0

para que pueda añadir fácilmente nuevas funcionalidades en assembly2 – nWorx

+0

que he añadido algo de código de ejemplo para usted que ayuda porque usted no tiene que anular o virtualizar cualquier método más -> más simple – nWorx

0

Parece que puede que tenga que utilizar un stragey o un patrón de plantilla para resolver este problema.

0

Existen varios patrones de diseño que podrían funcionar. Aquí está la lista que me vino a la mente (en este orden) mientras leía su publicación.

  1. ADAPTER
  2. DECORATOR
  3. TEMPLATE METHOD.
+0

Esto no explica por qué la llamada aparentemente no se está resolviendo de forma virtual. Vamos a diagnosticar antes de prescribir. –

3

Suponiendo que A llama a una instancia de ExtendedB que se hace referencia a través de una variable de tipo B, y el método está marcado virtual en B y override en ExtendedB, la llamada debe ser polimórficos. Deberia de funcionar. Quizás puedas mostrar algún código.

3

Es difícil de decir sin el código real, pero teniendo su diagrama (por cierto, me encanta el arte ASCII!) A su valor nominal, lo sugeriría que la clase A haga referencia a una interfaz en lugar de estar codificada de forma rígida para la clase B.

De esta manera:

// ----- this is in Assembly 1 ----------- 
public ClassA 
{ 
    public MyInterface myDependentObject; 
} 

public interface MyInterface 
{ 
    void OtherMethod(); 
} 

public class ClassB : MyInterface 
{ 
    public void OtherMethod() 
    { 
     // some code here... 
    } 
} 


// ----- this is in Assembly 2 ----------- 
public class OtherB : MyInterface 
{ 
    public void OtherMethod() 
    { 
     // some code here... 
    } 
} 

En su Asamblea 2, asigne Otrosb a claseA:

// ----- this is in Assembly 2 ----------- 
OtherB b = new OtherB(); 
ClassA a = new ClassA { myDependentObject = b }; 

Ahora cada vez claseA ejecuta el myDependentObject.OtherMethod(), será recogida en el montaje 2 en lugar de ensamblaje 1 Definiciones .

HTH ...

+1

Tener una referencia a una clase base no es difícil de codificar. Debería funcionar bien, así que no veo ninguna razón para complicar esto con las interfaces, especialmente cuando aún no sabemos por qué no funciona. –

+0

@Steven, el OP estaba pidiendo un patrón de diseño o un enfoque de mejores prácticas. En ese caso, seguiría usando interfaces. También creo que es bastante evidente por qué no funciona: ClassA está estableciendo una fuerte dependencia de ClassB en algún lugar de la Asamblea 1. Otra de las palabras, aquí tenemos un problema de "separación de preocupaciones". Interfaces es el mejor enfoque para manejar estos casos, porque afloja la dependencia y permite que estos se resuelvan más adelante en el proceso (es decir, en el Ensamblaje 2 según mi ejemplo). – code4life

+0

Pensé que la mejor práctica es utilizar la herencia cuando realmente desea la herencia, como hacemos aquí. Pero mi preocupación más profunda es que, por todo lo que hemos escuchado, la herencia con un método virtual debería haber conducido al polimorfismo, pero no fue así. Me gustaría entender por qué, antes de ir a la herencia y solucionar el problema. El problema descrito es * no * reproducible. –

1

¿Puede proporcionarnos algún código de reducción del ejemplo? Aprecio el arte ascii, pero sospecho que el problema puede estar en cómo A consigue la B en la que funciona. Si está creando una instancia de su propia copia, entonces no hay forma de que pueda invocar el método reemplazado en la clase derivada.

Dicho esto, también estoy de acuerdo en que la interfaz de refactorización sugerida por otros es el camino a seguir. Permita que A funcione desde la interfaz IB, por lo que debe usar un implementador proporcionado de IB, en lugar de generar el suyo propio. De esta forma, no importa si el objeto proporcionado es una B, una subclase de B o algo completamente diferente. También hace que las subclases A, B y Bs sean más comprobables, lo cual es Algo bueno.

+0

No, realmente no es. A veces desea heredar para poder anular selectivamente algunos métodos y no otros, y para que pueda encadenar selectivamente a la implementación base también. Las interfaces son algo bueno, pero no son un reemplazo directo de la herencia, ni cambiar a ellas nos ofrece una idea de por qué la herencia no era polimórfica. –

Cuestiones relacionadas