Versión simplificada del patrón de comando GOF, creada para fines de modernización de proyectos VCL. También agregó Action Factory a este proyecto, que está envolviendo un comando en la acción VCL.

El proyecto contiene dos versiones de la implementación del patrón:
ICommandTCommand basada en TComponent El componente TCommand fue creado para ayudar a la modernización del código VCL Legacy . Ayuda a la extracción de código enredado, que después de asegurarlo con pruebas unitarias, se puede refactorizar en más limpio y más barato para mantener el código orientado a los objetos.
El componente TCommand es un objeto de transición que debe refactorizarse después de borrar el código extraído y después de eliminar las dependencias de la interfaz de usuario

La forma más fácil de usar el componente TCommand es crear una nueva clase, pegar el método Long en el método Ejecutar y agregar todas las dependencias como propiedades publicadas. Ver muestra a continuación.
Diagrama del uso de TCommand en la aplicación VCL:

El desarrollador para construir un nuevo comando debe definir una nueva clase derivada de TCommand (Unit: Pattern.Command.pas ) e implementa un método protegido DoExecute , que contiene una lógica de comando principal.
El desarrollador también puede implementar un método DoGuard , que se llama antes de DoExecute y permitir verificar todas las inyecciones obligatorias (se explica el sistema de inyección a continuación). Por lo general, todas las inyecciones se verifican con la llamada de afirmación.
Comando de muestra sin inyección (guardia vacío):
type
TDiceRollCommand = class (TCommand)
protected
procedure DoExecute ; override;
end ;
procedure TDiceRollCommand.DoExecute ;
begin
ShowMessage( ' Dice roll: ' +RandomRange( 1 , 6 ).ToString);
end ; Para ejecutar el comando, debe crear objeto y llamar Execute el método público, que llame DoGuard y luego DoExecute :
cmd := TDiceRollCommand.Ceate(Self);
cmd.Execute; El componente TCommand ha incorporado un sistema de inyección automatizado basado en el mecanismo clásico RTTI utilizado por IDE Form Designer (Inspector de objetos). Las propiedades expuestas a ser inyectables deben definirse en la sección published del componente (comando). Todas las clases basadas en componentes han cambiado la generación de información de tipo de ejecución durante el proceso de compilación (opción del compilador {$TYPEINFO ON} ). Gracias a eso durante la creación de un nuevo comando, todas las dependencias se pueden proporcionar y asignarse fácilmente a las propiedades publicadas automáticamente. Se puede encontrar más información sobre el motor RTTI clásico en la documentación de Delphi: información de tipo de ejecución de tiempo de ejecución
Comando de muestra con dos dependencias (una requerida y otra opcional):
type
TDiceRollCommand = class (TCommand)
const
RollCount = 100 ;
private
fOutput: TStrings;
fProgressBar: TProgressBar;
procedure ShowProgress (aRoll: integer);
protected
procedure DoGuard ; override;
procedure DoExecute ; override;
published
property OutputRolls: TStrings read fOutput
write fOutput;
property ProgressBar: TProgressBar read fProgressBar
write fProgressBar;
end ;
procedure TDiceRollCommand.DoGuard ;
begin
System.Assert(fOutput<> nil );
end ;
procedure TDiceRollCommand.ShowProgress (aRoll: integer);
begin
if Assigned(fProgressBar) then begin
if aRoll= 0 then
fProgressBar.Max := RollCount;
fProgressBar.Position := aRoll;
end ;
end
procedure TDiceRollCommand.DoExecute ;
begin
ShowProgress( 0 );
for var i := 0 to RollCount- 1 do
begin
fOutput.Add(RandomRange( 1 , 7 ).ToString);
ShowProgress(i+ 1 );
end ;
end ;Las propiedades publicadas disponibles de TCommand coinciden con tipos de parámetros pasados en parámetros (matriz Open). Las siguientes reglas se utilizan mediante el algoritmo coincidente:
¡Advertencia! Se accede a los objetos inyectados por dirección en la memoria (puntero), gracias a que cualquier cambio realizado en el objeto es visible dentro y fuera del TCommand. Se accede a tipos y cadenas simples a través del valor y las propiedades deben actualizarse manualmente para actualizarse.
Muestra de código de inyección de código a las propiedades de TDICEROLLCOMMAND:
cmd := TDiceRollCommand.Create(Self)
.Inject([Memo1.Lines,ProgressBar1]);El método más popular y generalmente asesorado para inyectar dependencias es una inyección de constructor. Esta solución introducida aquí (patrón TCommand) es un enfoque más basado en componentes. Este patrón se parece más a una etapa de transición que permite extraer rápidamente y ejecutar partes importantes de una gran aplicación. El punto objetivo final en ese proceso es la mejor solución arquitectónica, significa inyección a través del constructor y usar interfaces en lugar de objetos.
TCommand.AdhocExecute<T> - Ejecuta un comando (crea un comando, inyecta dependencias lo ejecuta y elimina)Inject del método de llamadaExecuteTCommandAction que ejecuta el comando cuando se invoca la acciónTCommandAction Class es la acción clásica de VCL La lógica de negocios, extraída en el comando, se puede convertir fácilmente en comando asincrónico, procesada en un hilo de fondo separado. Reemplazar la clase TCommand con TAsyncCommand es primero en tal transformación:
uses
Pattern.AsyncCommand;
type
TAsyncDiceRollCommand = class (TAsyncCommand)
...
end ; Aunque el cambio es muy simple, pero en general, el procesamiento multiproceso es un tema mucho más serio y requiere un conocimiento más profundo de esta área. En este ejemplo ( TDiceRollCommand ) Dos temas son problemáticos:
fProgressBar: TProgressBarfOutputRolls: TStringsPuede lidiar fácilmente con ellos, pero esto requiere un conocimiento de procesamiento multiproceso más general. Más información que puede encontrar en documentación dedicada: comando asíncrono
TCommandAction es una clase de envoltura basada en TAction y es capaz de ejecutar comandos basados en la clase TCommand . El desarrollador, al construir la aplicación VCL, puede vincular fácilmente esta acción a muchos controles (componentes visuales que son impulsados por acciones o son conscientes de la acción). Por ejemplo, TCheckBox tiene una propiedad Action que se ejecuta cuando se usa está cambiando la casilla de verificación (marcado). Las acciones tienen algunas otras ventajas como el sistema de notificación Built, precisamente dos de estos motores: uno para actualizar el estado visual y otro, más interno, para notificar sobre la creación de nuevos y eliminación de los componentes existentes. Ambos motores son demasiado complejos para describirse en esta sección, se puede encontrar más información en la documentación en línea de Delphi.
Mirando la perspectiva arquitectónica de formulario TCommandAction se puede usar como un objeto de Invoker y después de la migración puede ser reemplazada por una solución personalizada más elástica.
Muestra de construcción en TCommandAction Invoker:
Button1.Action := TCommandAction.Create(Button1)
.WithCaption( ' Run sample command ' )
.WithCommand(TSampleCommand.Create(Button1))
.WithInjections([Memo1, Edit1]);| Método de utilidad | Descripción |
|---|---|
WithCaption(aCaption) | Establece un título de acción que se muestra en un control |
WithShortCut(aShortcut) | Establece un atajo que está activando una acción |
WithCommand(aCommand) | Establece un comando para ejecutar |
WithInjections(aInjections) | Inyecta valores en las propiedades del comando |
WithEventOnUpdate(aProc) | Evento activado después de la acción Evento en Update |
WithEventAfterExecution(aProc) | Evento activado cuando el comando estará terminado |
Evento de configuración de muestra ONUPDATE en TCommandAction :
Button2.Action := TCommandAction.Create(Self)
.WithCaption( ' Run sample command ' )
.WithCommand(MySampleCommand)
.WithEventOnUpdate(
procedure(cmd: TCommandAction)
begin
cmd.Enabled := CheckBox1.Checked;
end );El patrón TCommand permite a los desarrolladores extraer el valioso código de negocio y hacer que las aplicaciones sean menos acopladas. Simultáneamente, los desarrolladores aún pueden usar prácticas de componentes bien conocidas y componer un código más complejo utilizando componentes de comando. Los desarrolladores incluso pueden expandir el patrón de comando con sus propias propiedades y eventos. Sin embargo, este enfoque es una solución temporal y debe evolucionar en un diseño más orientado a objetos.
El patrón TCommand es compatible con el patrón de comando GOF (ver diagramas anteriores) y se puede modernizar. Esta moderación debe iniciarse cuando la fase de refactorización se finalice y la lógica estará cubierta por pruebas unitarias. Durante la refactorización, se deben eliminar todas las dependencias visuales, también todas las dependencias irrelevantes y el código debe romperse en métodos o clases más lógicos más pequeños.
Después de la modernización, todas las dependencias deben inyectarse a través del constructor, se debe acceder al comando a través de la interfaz, el acceso a los elementos internos del comando debe ser a través de los métodos de Getter y Setter. Los objetos compuestos deben crearse usando el contenedor DI, como el método Spring4D GlobalContainer .
Ejecución de comando ad-hoc (crear, inyectar, ejecutar, eliminar)
TCommand.AdhocExecute<TSampleCommand>([Memo1, Edit1]);Crea dependencias de comando e inyectores:
cmdSampleCommand := TSampleCommand.Create(AOwner);
cmdSampleCommand.Inject([Memo1, Edit1]); Ejemplo de componente TCommand :
type
TSampleCommand = class (TCommand)
private
FMemo: TMemo;
FEdit: TEdit;
protected
procedure DoGuard ; override;
procedure DoExecute ; override;
published
property Memo: TMemo read FMemo write FMemo;
property Edit: TEdit read FEdit write FEdit;
end ;
procedure TSampleCommand.DoGuard ;
begin
System.Assert(Memo<> nil );
System.Assert(Edit<> nil );
end ;
procedure TSampleCommand.DoExecute ;
begin
Memo.Lines.Add( ' Getting Edit text and put it here ... ' );
Memo.Lines.Add( ' * Edit.Text: ' +Edit.Text);
end ;