Virtualidad vs. Eventos

El otro día, un amiguete desarrollador, me escribia lo siguiente:

«Estoy discutiendo con Manolo una cosa sobre que es mejor, te cuento nuestro problema:

Tenemos un formulario base, que realiza una serie de acciones, y tengo un método de insertar que para todos los formulario lo hace igual.

El resto de los formularios los heredamos del formulario base, pues bien, queremos poden, en algún caso especial, que después de la inserción en el formulario hijo se hagan una serie de acciones. Yo digo que es mejor crearse en la clase base un método virtual y sobreescribirlo, en una clase hija.  Manolo dice que es mejor tener un evento y un delegado.

¿Puedes aclararnos quien de los 2 tiene razón?»

Esta pregunta suele ser habitual, por eso público esta entrada. Por eso y porque quiero conocer vuestra opinión. Hay una serie de situaciones donde saber si es mejor usar un método virtual o un evento es dificil.

Yo respondí lo siguiente:

«Que cosas tiene la vida…

Los dos tenéis razón y los dos estáis equivocados… interesante paradoja no…

¿Por qué los dos tenéis razón?: Los dos mecanismos son muy similares. La diferencia es sutil. Si buscas un mecanismo para modificar el comportamiento de una clase mediante herencia el camino es la virtualidad, si buscas un camino para que otras clases se enteren que esta haciendo tu clase eso es un evento. Lo dicho hasta ahora ya lo sabéis… la clave está en que el evento no permite sobreescribir totalmente el comportamiento, y el método virtual sí, si necesitáis esto yo usaria virtualidad (se trata de algo similar al clásico patrón strategy).

Véis que hay una clara cuestión semantica: si publicas un evento estás diciendo: quiero que mi clase le cuente cosas al mundo, si publicas un método virtual estás diciendo al mundo que puede modificar el comportamiento de tu clase y que ha sido diseñada para soportar esa posibilidad. ¿Qué es lo que queréis comunicar a quien use vuestra clase?.

Otra cuestión relevante es que ambos mecanismos permiten cambiar el comportamiento de la clase base, cambiar que ocurre en ciertas ocasiones… pero la virtualidad exige heredar y por tanto es un mecanismo que solo se resuelve en tiempo de compilación, sin embargo el evento se puede subscribir y desubscribir a voluntad en tiempo de ejecución, quzás necesitéis esa capacidad en algún escenario.

¿Por qué los dos estáis equivocados en cierto modo?: Por qué no sol soluciones excluyentes entre sí. Quizás la mejor solución sea usar un evento. Paradojico ¿no? Os cuento el porqué: el patrón de .net para implementar eventos dice que todo evento debe tener asociado un método protegido virtual que permita sobrescribir en clases derivadas el comportamiento de invocación del evento.

Conclusión: Si tiraís por el evento, tenéis que tener también un método virtual, o sea, lo dos ganáis. Tenéis los dos mecanismos y dáis, con casi nulo esfuerzo, mayor flexibilidad a los que usen vuestra clase.

Lo mejor del caso es que con el evento, además estáis implementando dos patrónes: polimorfismo (mediante virtualidad) y observación (mediante el evento), de tal manera que es quien usa la clase quien elige que mecanismo es el que más se adecua en cada situación.

Otra opción sería usar IoC y un inyector de dependencias, tipo Unitiy, pero creo que eso es, en este caso, harina de otro costal. Un inyector de dependencias os permitiria cambiar el comportamiento mediante configuración, sin tocar el código, pero no creo que esto sea lo necesitáis. Aunque sea la solución de moda.»

¿Compartís mi visión del asunto? ¿Qué soléis preferir en situaciones de este tipo, virtualidad o eventos?. Solo como comentarío añadido decir que mi compañero Unai es más partidario de usar solo virtualidad para el caso que nos ocupa…

20 comentarios sobre “Virtualidad vs. Eventos”

  1. Yo soy mas partidario de usar funciones virtuales, creo que son mas flexibles y le das al desarrollador de la clase heredada mas independencia de la clase base, ya que, probáblemente, no deba tener acceso al funcionamiento interno de la clase base.
    Además Manolo está hablando de realizar acciones después de insertar, entonces recomiendo virtualizar:

    public class ClaseBasica
    {
    public virtual void insert()
    { }
    }

    public class ClaseHeredada:ClaseBasica
    {
    public override void insert()
    {
    base.function();
    //Añadir funcionalidad despues de insertar
    }
    }

    También creo que es una estructura mas legible y de mayopr calidad.

  2. Miguel tu código tiene un pequeño problema y es la misma que dice Rodrigo en el sentido de que te permite sobreescribir el funcionamiento de la clase base. Deja en manos de la clase heredada hacer la llamada a la clase base y si no lo hace, rompe la funcionalidad heredada: la de insert(). Para solucionar este tema existe un patrón que se llama plantilla (template):

    public class ClaseBasica

    {
    public virtual DespuesDeInsert() {};
    public virtual void insert()

    {
    // Hacer cosas de Insert
    DespuesDeInsert();
    }

    }

    public class ClaseHeredada:ClaseBasica

    {

    public override void DespuesDeInsert()

    {
    // haz lo que quieras

    }

    }

    De esta forma, aseguras que la clase hija no modifica el comportamiento original de la clase padre y a la vez permite añadir funcionamiento adicional. Si todas las clases heredadas necesitan implementar DespuesDeInsert por fuerza, entonces hazlo abstracto.

    Por otra parte, Rodrigo, no creo que IoC resuelva algo en esta situación. IoC es para invertir el control, no para inyectar comportamiento. Y desde luego no se debe usar porque algo esté de moda. Y mucho menos si no es la solución apropiada.

  3. Ambas soluciones son buenas y cumplen su objetivo, pero creo que hay algo que no estamos teniendo en cuenta,
    ¿que seria lo mas «natural» para el programador que escriba el formulario derivado?
    Personalmente creo que en este tipo de situaciones, lo natural para un desarrollador seria buscar evento y suscribirse a él.
    Por lo tanto en mi caso preferiria un evento.

  4. DevJoke,

    «¿que seria lo mas «natural» para el programador que escriba el formulario derivado?»

    ¿Y porque es un evento? Independientemente, el evento es un procedimiento que se ejecuta en respuesta a una acción, la de inserción. Al final y al cabo, el método de implementarlo es usando el patrón plantilla, pero sin la necesidad de introducir un evento.

    Justamente tener formularios y controles con 20.000 eventos es lo que hará que el código sea ilegible.

  5. En este caso particular coincido con el comentario anonimo de utilizar el patron template.
    Si estoy heredando de un clase, pienso que la manera de añadir comportamiento a las operaciones base es utilizando el patron template.
    Otra cosa es si quiero cambiar el comportamiento de un objeto desde fuera, en ese caso si que veo mejor el uso de eventos. Por ejemplo en el caso tipico del onclick, al boton instanciado le puedo decir que hacer cuando se produzca el onlclick suscribiendome al evento.
    Saludos.

  6. Según entiendo el problema, tenemos un formulario base que realiza inserciones, y en casos particulares queremos hacer otro tipo de operaciones.

    Es aqui donde esta el problema, en la naturaleza de esas «otras operaciones».

    Por ejemplo, si lo que queremos hacer despues de la inserción es actualizar un grid de datos para que muestre en pantalla los nuevos registros – para mi – lo «natural» seria un evento «AfterInsert», en el que refrescar los datos. Por supuesto, crear el método virtual y sobreescribirlo es otra solución igual de válida , pero en este caso creo que menos «intuitiva».

    Otro escenario totalmente distinto seria que queramos modificar el comportamiento del insert, por ejemplo para hacer una segunda insercion en otra tabla. En este caso seria mejor un método virtual, que nos permitirá envolver las dos operaciones en una única transacción.

    Con esto llegamos a la recomendación de MicroSoft, tener el evento y la posibilidad de sobreescribir el método que lo invoca.

    Sobre lo que dice el comentarios anonimo :
    «Justamente tener formularios y controles con 20.000 eventos es lo que hará que el código sea ilegible.»
    Ese codigo resultará igual de complejo e ilegible en los dos casos.

  7. Según yo tengo entendido, el proposito del post es este problema:

    «Otro escenario totalmente distinto seria que queramos modificar el comportamiento del insert, por ejemplo para hacer una segunda insercion en otra tabla. En este caso seria mejor un método virtual, que nos permitirá envolver las dos operaciones en una única transacción»

    y la solución ofrecida originalmente (la que tu llamas hacer un método virtual) tiene el defecto de que si se opta por hacer un eso, se puede cambiar la funcionalidad del padre y no es lo que el amigo de Rodrigo preguntó originalmente. Preguntó como puede extenderlo, hacer una serie de cosas adicionales.

  8. Hombre, a ver, si que es cierto que yo comento un escenario concreto en el post, pero si estás diseñando una librería, como es el caso, lo lógico es pensar en que quien use tu clase puede querer cubrir otros escenarios… por eso yo opté por el evento. Con poco coste cubro más escenarios…

    En cualquier caso, interesantísima la discusión.

    Por ejemplo yo no había pensado en la posibilidad de usar el patrón plantilla (template). Es una buena opción porque refina la virtualidad pura evitando que alguien se pueda comer la llamada a la clase base… aunque también es cierto que puede ser que nos interese que esa llamada a la funcionalidad de la clase base sea opcional…

    Un saludo!

  9. Siempre relacione los eventos y delegados con los triggers de las bases de datos por eso mismo, pense que para la realizar una acción concreta despues de una inserción, lo mas logico seria realizar un evento post similar a los un triger insert, ya que me parecia lo mas natural, ademas no habia código compartido por otros formularios, asi que pense que el metodo virtual seria menos natural, ya que todos los formularios podrian acceder a el y no tendria nada de código, pero claro «el muy cab… de mi compañero, como siempre llevandome la contraria, empezo a decir que no tenia razón, que lo más lógico seria utilizar un metodo virtual y bla, bla…», como en la mayoria de las ocasiones no nos ponemos deacuerdo y con el fin de terminar la discusión le dije: Bien, preguntale a Rodrigo, y nos ceñiremos a lo que opine el, que lo sabe mejor que nosotros…

    Para colmo el tambien se lo envio a Unai que respondio rápidamente diciendo que para el lo mas adecuado era un metodo virtual, lo que tuve que aguantar hasta que recibio tu correo al dia siguiente…

    En fin la culpa la tengo yo, por no saber dar ordenes, he de comprarme una vara de avellano…

    En fin, se que tengo que tener paciencia con el, el pobre tuvo la mala suerte de nacer en «Solares», una zona de Cantabria, donde solo habitaban 4 membrillos que luego fueron conquistados por los Romanos, y ahora mirarle, discutiendo sobre delegados y metodos virtuales, cualquier dia le tengo que aguantar dando una charla sobre «Cobol.net», la verdad es que el tio, tiene merito….

  10. Por aclarar algo el problema, queriamos implementar funcionalidad para controlar procesos que se produzcan antes y despues de insertar, eliminar, duplicar y modificar registros en nuestros formularios, siempre serían acciones que se realizan antes o despues de estos procesos de forma similar a los triggers Insert, Update, y Delete, pense que un evento y delegado seria los mas adecuado para implementar estas caracteristicas, ya que van a utilizar en determinados casos y por supuesto no aprovechan ningún código en común, como bien dice Rodrigo, la solución mas adecuada es aplicar el pàtrón de .net, con lo que tienes que implementar tambien una función virtual, «es curioso que el fxcop no avise de esto», con lo cual utilizamos lo mejor de ambos metodos, un paso mas alla que conjuga lo mejor de ambas propuestas. Muchas gracias Rodrigo.

  11. Lo se, y te lo agradezco, pero el elemento este, le ha mandado la consulta con hasta el mismisimo Bill Gates, que me acaba de llamar y me dice que por favor, que le dejemos empaz, que usemos los putos delegados de los cojones, que para esos los inventaron, y que han colapsado los servidores de correo con la misma pregunta a todos los desarrolladores de Microsoft, asi que como veo que esto se va a hacer público, que mas da…. Gracias de nuevo.

  12. Queridisimo jefe:

    Tenia pensado en hacerlo y que mi compañero me lleva la contraria difiere de: «Tienes que hacer todas estas cosas y hazlo como creas conveniente», una vez echo y estando funcionando, (esta parte culpa mira), te digo mira como he echo esto ¿que te parece…? y va y se te ocurre «no me gusta como lo tienes echo, mejor los eventos»… y luego me dices que yo discuto… Jefes no puedes vivir sin ellos pero con ellos tampoco…

    Como te he recordado muchas veces, mas vale ser de Solares que por lo menos tenemos un agua no contaminada, que no desearlo… el pobrecito intento venir a Solares y no le dejaron cruzar la frontera y se tuvo que ir a Torrelavega… en fin que ya empezamos a estar seniles, hacerse mayor es lo que tiene…

    PD. Me ha comentado Bill que te dediques a matar al Zorro ese…

  13. No sino me mosqueo… esta bien eso de discutir, por lo menos se te hace mas ameno la jornada al ritmo del tong tong del tambor (como en los barcos de remos) je je je… y te escucha (aunque no te haga caso)… Pero bueno como se suele decir al jefe ni agua y en desierto polvorones… lo mejor seria tener un jefe que sea del departamento de sistemas (lo siento por los de sistemas tengo grandisimas experiencias)

  14. Excelente respuesta. Obviamente, qué es mejor en cada caso dependerá (verbigracia) del caso, y por eso el patrón de implementación de diseño que propone (y utiliza siempre) MS permite que utilicemos lo más conveniente al caso.

    Para una clase que quiere «enterarse» de que el formulario graba (pero sin modificar el proceso, a lo sumo cancelarlo) será mejor un evento.

    Para una clase derivada que quiera modificar o extenderlo, la sobreescritura es lo más claro. Piensen qué difícil de leer sería el caso de una clase derivada que escucha el evento Grabar de la base, lo cancela y graba algo completamente diferente.

Responder a lfraile Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *