2009-12-16 14 views
9

Actualmente estoy escribiendo una pequeña biblioteca de C# para simplificar la implementación de pequeñas simulaciones físicas/experimentos.Métodos o eventos virtuales en C#

El componente principal es un SimulationForm que ejecuta un ciclo de temporizador internamente y oculta el código repetitivo del usuario. El experimento en sí se acaba de definirse a través de tres métodos:

  1. Init() (Inicializar todo)
  2. Render(Graphics g) (Render estado actual de la simulación)
  3. Move(double dt) (Mover experimento para dt segundos)

I Sólo me pregunto cuál es la mejor opción para dejar que el usuario implemente estas funciones:

1) métodos virtuales a ser anulados por la forma heredera

protected virtual void Init() {} 
... 

o

2) Eventos

public event EventHandler<MoveSimulationEventArgs> Move = ... 
... 

Editar: Tenga en cuenta que los métodos no deben ser abstracta de todos modos. De hecho, hay aún más y ninguno de ellos tiene por implementar. A menudo es conveniente dejarlos fuera debido a que muchas simulaciones no los necesitan.

Lo bueno de esto es una "forma normal" es que se puede escribir

partial class frmMyExperiment : SimulationForm { 
} 

y que está perfectamente capaz de interactuar con el diseñador y todos los controles heredados y los ajustes/propiedades. No quiero perder estas características teniendo un enfoque completamente diferente.

Respuesta

17

Prefiero usar métodos virtuales en este caso.

Además, si los métodos son requerido para funcionar, puede hacer que su clase sea abstract class. Este es el mejor en términos de usabilidad, ya que hace que el compilador haga cumplir el uso. Si el usuario intenta usar su clase/Formulario sin implementar los métodos, el compilador se quejará.

Los eventos serían un poco inapropiados aquí, ya que esto realmente no es algo en lo que desea más de una implementación única (los eventos permiten múltiples suscriptores), y conceptualmente es más funcional, y menos una notificación causada por el objeto.

+2

+1 para 'abstract' en los métodos requeridos. –

+1

IMO, Resumen FTW. – Yoopergeek

+0

El resumen no encaja, pero el "a funcional pero una notifcación" es un buen punto +1 – Dario

3

Hay otra opción, tomada de la programación funcional: exponer los puntos de extensión como propiedades de tipo Func o Action, y hacer que sus experimentos proporcionen delegados de esta manera.

Por ejemplo, en el corredor de la simulación, usted tendría que:

public class SimulationForm 
{ 
    public Action Init { get; set; } 
    public Action<Graphics> Render { get; set; } 
    public Action<double> Move { get; set; } 

    //... 
} 

Y en su simulación usted tendría que:

public class Simulation1 
{ 
    private SimulationForm form; 

    public void Simulation1() 
    { 
     form = new SimulationForm(); 
     form.Init = Init; 
     form.Render = Render; 
     form.Move = Move; 
    } 

    private void Init() 
    { 
     // Do Init code here 
    } 

    private void Render(Graphics g) 
    { 
     // Do Rendering code here 
    } 

    private void Move(double dt) 
    { 
     // Do Move code here 
    } 
} 

Editar: Para un ejemplo muy tonto de exactamente esta técnica que utilicé en algún código privado (para un proyecto privado), mira my delegate-powered-collection blog post.

+0

+1 para una nueva idea. No estoy seguro si es el "mejor" enfoque. – ScottS

+0

No sé si es el mejor enfoque tampoco, solo quería señalar otra alternativa. –

+1

@Erik: Comencé mi comentario con "He utilizado esta estrategia para algunos problemas en particular en el pasado:" recuerde que esta no es una de las formas más comunes en que la gente piensa para extender programas, lo que puede llevar a cierta confusión sobre cómo una API está destinada a ser utilizada. Es importante sopesar las ventajas de esto frente a la posibilidad de que otros desarrolladores tengan que perder tiempo tratando de comprenderlo. :) –

5

Un poco de ayuda para decidir:

¿Quieres notificar a otros (múltiples) objetos? Luego usa eventos.

¿Desea hacer posibles diferentes implementaciones/use polimorfismo? Luego usa métodos virtuales/abstractos.

En algunos casos, incluso combinidng en ambos sentidos es una buena solución.

13

Curiosamente, he tenido que hacer una elección similar en un diseño en el que trabajé recientemente. Un enfoque no es estrictamente superior a otro.

En su ejemplo, dado que no hay información adicional, probablemente elegiría métodos virtuales, particularmente porque mi intuición me lleva a creer que usaría la herencia para modelar diferentes tipos de experimentos, y no necesita la capacidad de tener múltiples suscriptores (como lo permiten los eventos).

Estas son algunas de mis observaciones generales acerca de la elección entre estos patrones:

eventos son grandes:

  1. cuando no se necesita la persona que llama para devolver cualquier información, y cuando quieres extensibilidad sin necesidad de subclases.
  2. si desea permitir que la persona que llama tenga varios suscriptores que puedan escuchar y responder al evento.
  3. porque se organizan naturalmente en el patrón template method, lo que ayuda a evitar la introducción del fragile base class problem.

El mayor problema con los eventos es que administrar la vida útil de los suscriptores puede ser complicado, y puede presentar fugas e incluso defectos funcionales cuando los suscriptores se mantienen suscritos por más tiempo de lo necesario. El segundo problema más grande es que permitir múltiples suscriptores puede crear una implementación confusa en la que los suscriptores individuales se pisan entre sí o exhiben dependencias de órdenes.

métodos virtuales funcionan bien:

  1. cuando sólo desea herederos sean capaces de alterar el comportamiento de una clase.
  2. cuando se necesita para devolver información de la llamada (que el evento de no admiten fácilmente)
  3. cuando sólo desea un solo abonado a un punto de extensión en particular
  4. cuando se quiere derivados de derivados para poder anular comportamiento.

El mayor problema con los métodos virtuales es que puede introducir fácilmente el problema de la clase base frágil en su implementación. Los métodos virtuales son esencialmente un contrato con clases derivadas que debe documentar claramente para que los herederos puedan proporcionar una implementación significativa.

El segundo mayor problema con los métodos virtuales es que puede introducir un árbol de herencia profundo o amplio solo para personalizar cómo se personaliza el comportamiento de una clase en un caso particular. Esto puede estar bien, pero generalmente trato de evitar la herencia si no hay una relación clara is-a en el dominio del problema.

Hay otra solución que podría considerar usar: el strategy pattern. Haga que su clase soporte la asignación de un objeto (o delegado) para definir cómo deben comportarse Render, Init, Move, etc. Su clase base puede suministrar implementaciones predeterminadas, pero permite que los consumidores externos modifiquen el comportamiento. Si bien es similar al evento, la ventaja es que puede devolver valor al código de llamada y solo puede exigir un solo suscriptor.

2

ninguno.

debe consumir una clase que implemente una interfaz que defina las funciones que necesita.

1

¿Por qué la interfaz no es una opción para esto?

0

Utilice un método virtual para permitir una especialización de la clase base: los detalles internos.

Conceptualmente, puede usar eventos para representar el 'resultado' de una clase, además de los métodos de retorno.

0

Me gusta el resumen de LBuskin. Con respecto al problema de la clase base frágil, hay un caso en el que estoy pensando eventos protegidas puede ser un candidato para las mejores prácticas:

  1. tienes eventos realmente destinados al uso de clases interna (en contraposición a las estrategias quizá como el de la OP Render), tales como al inicializar o EnabledChanging,
  2. usted tiene el potencial para una jerarquía de herencia grande (profunda y/o amplia), con varios niveles de necesidad de actuar en el evento (múltiples no públicas consumidores!)
  3. no lo haces y confiar en cada clase derivada recordando invocar base.OnSomethingHappened(). (Evitar frágil base de clase problema.)
  4. fin de manejar el evento no importa (de lo contrario usar una cadena de base.OnSomethingHappened() para controlar el orden.)
  5. el evento se aplica a algunos tipo de cambio de estado interno y no sería significativo para una clase externa (de lo contrario, utilice un evento público ).Por ejemplo, un evento de inicialización pública normalmente sería un evento cancelable que ocurre antes de que ocurra cualquier inicialización interna. Un evento de Inicialización protegida no debe exponerse al usuario de la clase como un evento cancelable porque la clase ya puede estar parcialmente inicializada.

Esto evita la necesidad de documentar métodos protegidos virtuales con "clases derivadas deben llamar al método de base" y la esperanza de que usted u otras personas que obtienen clases siempre leer la documentación y lo sigue. También evita la incertidumbre al preguntarse si la llamada al método base debe invocarse al comienzo del método reemplazado, o en el medio o al final.

Nota: Si cree que una clase base vinculada a su propio evento es tonta, no hay nada que le impida tener un método no virtual en la clase base.

En el caso del OP, creo que los eventos Init y posiblemente Move podrían cumplir los criterios anteriores según las circunstancias. Normalmente, es poco probable que tenga más de un método Render, por lo que un método abstracto (o método virtual vacío, si esta clase te permite ejecutar simulaciones sin dibujar nada) método que no necesitaba invocar ningún método Render parece más probable aquí . Una estrategia (delegado público) para Render sería útil si los componentes externos pudieran saber cómo dibujar la clase en el objetivo gráfico, tal vez con un estilo visual alternativo (es decir, 2D frente a 3D). Parece poco probable para un método Render, pero tal vez se pueda aplicar a otras funciones no mencionadas, especialmente aquellas que aplican un algoritmo (método de promedio de línea de tendencia, etc.).

Cuestiones relacionadas