delphi vb6 coding-style realbasic

delphi - ¿Por qué es una mala práctica llamar a un manejador de eventos desde el código?



vb6 coding-style (9)

Supongamos que tiene un elemento de menú y un botón que hacen la misma tarea. ¿Por qué es una mala práctica colocar el código de la tarea en un evento de acción de control y luego hacer una llamada a ese evento desde el otro control? Delphi permite esto como vb6 pero realbasic no y dice que debes poner el código en un método que luego es llamado tanto por el menú como por el botón


¿Por qué es una mala práctica? Porque es mucho más fácil reutilizar el código cuando no está incrustado en los controles de la interfaz de usuario.

¿Por qué no puedes hacerlo en REALbasic? Dudo que haya alguna razón técnica; es probable que solo sea una decisión de diseño que tomaron. Ciertamente impone mejores prácticas de codificación.


Es más limpio obviamente. Pero la facilidad de uso y la productividad son, por supuesto, siempre importantes.

En Delphi generalmente me abstengo de hacerlo en aplicaciones serias, pero llamo a los manejadores de eventos pequeños. Si algo pequeño se transforma de algún modo en algo más grande, lo limpio y, al mismo tiempo, aumenta la separación de la IU lógica.

Aunque sé que no importará en Lazarus / Delphi. Otros idiomas pueden tener un comportamiento más especial asociado a eventhandlers.


Es una cuestión de cómo está organizado su programa. En el escenario que describió, el comportamiento del elemento de menú se definirá en términos de los botones:

procedure TJbForm.MenuItem1Click(Sender: TObject); begin // Three different ways to write this, with subtly different // ways to interpret it: Button1Click(Sender); // 1. "Call some other function. The name suggests it''s the // function that also handles button clicks." Button1.OnClick(Sender); // 2. "Call whatever method we call when the button gets clicked." // (And hope the property isn''t nil!) Button1.Click; // 3. "Pretend the button was clicked." end;

Cualquiera de esas tres implementaciones funcionará, pero ¿por qué el elemento del menú debe depender tanto del botón? ¿Qué tiene de especial el botón que debería definir el elemento del menú? Si un nuevo diseño de interfaz de usuario eliminó los botones, ¿qué pasaría con el menú? Una mejor manera es factorizar las acciones del manejador de eventos para que sea independiente de los controles a los que está conectado. Hay algunas maneras de hacerlo:

  1. Una es deshacerse por completo del método MenuItem1Click y asignar el método Button1Click a la propiedad del evento MenuItem1.OnClick . Es confuso tener métodos nombrados para botones asignados a eventos de elementos de menú, por lo que querrá cambiar el nombre del controlador de eventos, pero eso está bien, porque a diferencia de VB, los nombres de métodos de Delphi no definen qué eventos manejan. Puede asignar cualquier método a cualquier manejador de eventos siempre que las firmas coincidan. Los eventos OnClick ambos componentes son del tipo TNotifyEvent , por lo que pueden compartir una única implementación. Nombra los métodos para lo que hacen, no para lo que pertenecen.

  2. Otra forma es mover el código del controlador de eventos del botón en un método separado y luego llamar a ese método desde los controladores de eventos de ambos componentes:

    procedure HandleClick; begin // Do something. end; procedure TJbForm.Button1Click(Sender: TObject); begin HandleClick; end; procedure TJbForm.MenuItem1Click(Sender: TObject); begin HandleClick; end;

    De esta forma, el código que realmente hace las cosas no está vinculado directamente con ninguno de los componentes, y eso le da la libertad de cambiar esos controles más fácilmente , como al cambiarle el nombre o reemplazarlos por controles diferentes. Separar el código del componente nos lleva a la tercera manera:

  3. El componente TAction , introducido en Delphi 4, está diseñado especialmente para la situación que ha descrito, donde hay varias rutas de UI para el mismo comando. (Otros lenguajes y entornos de desarrollo proporcionan conceptos similares, no son exclusivos de Delphi). Ponga su código de manejo de eventos en el controlador de eventos OnExecute , y luego asigne esa acción a la propiedad Action tanto del botón como del elemento del menú.

    procedure TJbForm.Action1Click(Sender: TObject); begin // Do something // (Depending on how closely this event''s behavior is tied to // manipulating the rest of the UI controls, it might make // sense to keep the HandleClick function I mentioned above.) end;

    ¿Desea agregar otro elemento de IU que actúa como el botón? No hay problema. Agréguelo, establezca su propiedad de Action y termine. No es necesario escribir más código para que el nuevo control se vea y actúe como el anterior. Ya has escrito ese código una vez.

    TAction va más allá de los controladores de eventos. Le permite asegurarse de que los controles de su UI tengan una configuración de propiedad uniforme , incluidos títulos, sugerencias, visibilidad, habilitación e íconos. Cuando un comando no es válido en ese momento, configure la propiedad Enabled la acción en consecuencia, y los controles vinculados se desactivarán automáticamente. No hay necesidad de preocuparse por un comando que se deshabilita a través de la barra de herramientas, pero todavía se habilita a través del menú, por ejemplo. Incluso puede usar el evento OnUpdate la acción para que la acción pueda actualizarse en función de las condiciones actuales, en lugar de que necesite saber cada vez que sucede algo que podría requerir que configure la propiedad Enabled inmediato.


Esta es una respuesta de extensión, como se prometió. En 2000 comenzamos a escribir una aplicación usando Delphi. Esto fue un EXE y pocos DLL que contienen lógica. Esta era la industria del cine, por lo que había clientes DLL, DLL de reserva, DLL de taquilla y DLL de facturación. Cuando el usuario quería hacer la facturación, abría la forma apropiada, seleccionaba el cliente de una lista, luego la lógica OnSelectItem cargaba los teatros de los clientes en el siguiente cuadro combinado, y luego seleccionaba el siguiente evento OnSelectItem lleno del tercer cuadro combinado con información sobre las películas, eso no ha sido facturado todavía La última parte del proceso fue presionar el botón "Do Invoice". Todo se hizo como un procedimiento de evento.

Entonces alguien decidió que deberíamos tener un extenso soporte de teclado. Hemos agregado controladores de eventos de llamadas de otros controladores pares. El flujo de trabajo de los controladores de eventos comenzó a complicarse.

Después de dos años, alguien decidió implementar otra función, de modo que el usuario que trabaja con datos de clientes en otro módulo (módulo de clientes) debe recibir un botón titulado "Facturar a este cliente". Este botón debe disparar el formulario de factura y presentarlo en ese estado, como si fuera un usuario que ha estado seleccionando manualmente todos los datos (el usuario debía poder mirar, hacer algunos ajustes y presionar el botón mágico "Hacer factura" ) Dado que los datos de los clientes eran una DLL y la facturación era otra, era EXE el que transmitía los mensajes. Entonces, la idea obvia fue que el desarrollador de datos del cliente tendrá una sola rutina con ID única como parámetro, y que toda esta lógica estará dentro del módulo de facturación.
Imagina lo que sucedió Dado que TODA la lógica estaba dentro de los manejadores de eventos, pasamos gran cantidad de tiempo, intentando realmente no implementar lógica, pero tratando de imitar la actividad del usuario, como elegir elementos, suspender Application.MessageBox dentro de manejadores de eventos usando variables GLOBAL, y así sucesivamente. Imagínese: si tuviéramos incluso procedimientos lógicos simples llamados manejadores de eventos internos, podríamos haber introducido la variable booleana DoShowMessageBoxInsideProc en la firma del procedimiento. Tal procedimiento podría haberse llamado con un parámetro verdadero si se llama desde el controlador de eventos y con parámetros FALSE cuando se llama desde un lugar externo.

Así que esto es lo que me ha enseñado a no poner la lógica directamente dentro de los manejadores de eventos GUI, con la posible excepción de pequeños proyectos.


Otra gran razón es para la capacidad de prueba. Cuando el código de manejo de eventos está oculto en la IU, la única manera de probar esto es a través de pruebas manuales o automáticas que están estrechamente relacionadas con la IU. (por ejemplo, abra el menú A, haga clic en el botón B). Cualquier cambio en la IU, naturalmente, puede romper docenas de pruebas.

Si el código se refabrica en un módulo que se ocupa exclusivamente del trabajo que necesita realizar, entonces las pruebas se vuelven mucho más fáciles.


Porque debe separar la lógica interna de alguna otra función y llamar a esta función ...

  1. desde ambos controladores de eventos
  2. por separado del código si necesita

Esta es una solución más elegante y es mucho más fácil de mantener.


Suponga que en algún momento decide que el elemento del menú ya no tiene sentido y desea deshacerse del elemento del menú. Si solo tiene otro control que apunta al controlador de eventos del elemento de menú, que podría no ser un gran problema, puede simplemente copiar el código en el controlador de eventos del botón. Pero si tiene varias formas diferentes de invocar el código, tendrá que hacer muchos cambios.

Personalmente, me gusta la forma en que Qt maneja esto. Hay una clase de QAction con su propio controlador de eventos que se puede enganchar y, a continuación, la QAction se asocia con los elementos de la UI que necesitan para realizar esa tarea.


Supongamos que en algún momento decidiste que el menú debería hacer algo ligeramente diferente. Quizás este nuevo cambio solo ocurra bajo algunas circunstancias específicas. Te olvidas del botón, pero ahora has cambiado su comportamiento también.

Por otro lado, si llama a una función, es menos probable que cambie lo que hace, ya que usted (o el siguiente tipo) sabe que esto tendrá malas consecuencias.


Separación de intereses. Un evento privado para una clase debe ser encapsulado dentro de esa clase y no debe ser llamado desde clases externas. Esto hace que su proyecto sea más fácil de cambiar en el futuro si tiene interfaces fuertes entre objetos y minimiza las ocurrencias de puntos de entrada múltiples.