El patrón Command en Angular con manager e invoker específico

Tal como pudimos ver en el anterior artículo sobre el patrón command, pueden existir multitud de servicios de este tipo dispersos por la aplicación y en consecuencia en la parte cliente tendríamos que inyectar varios de esos servicios  allá donde se requiera.

Para atajar esta locura, crearemos un manager (gestor,  almacén) que tendrá todos los comandos necesarios, con  lo que sólo tendremos que inyectar un único servicio en el cliente además de poder gestionar mejor todos los comandos.

Para no repetir código cada vez que el cliente haga modificaciones, la selección del comando y su ejecución se realizará dentro de un invoker específico derivando del invoker genérico.

Comandos

Para empezar, deberemos crear un enumerado que contenga el nombre de todos los comandos.

export enum CommandName {
    CreateHeader = 'CreateHeader',
    ModifyTotalAmount = 'ModifyTotalAmunt'
}

Esto nos servirá para identificar cada comando de forma única.

Nombre del comando

Obligaremos que cada comando se identifique modificando la interfaz ICommand para que devuelva su nombre.

export interface ICommandService {
   getCommandName: () => CommandName;
   execute(): Promise<void>;
}

A partir de aquí será necesario modificar cada comando para devolver su nombre.

export class ModifyTotalAmountCommandService implements ICommandService {
 
...
  public getCommandName = () => CommandName.ModifyTotalAmount;
...
}
export class CreateHeaderCommandService implements ICommandService {
 
...
  public getCommandName = () => CommandName.CreateHeader;
...
}

Y así con cada comando que exista.

Command Manager

Como hemos dicho antes, este gestor de comandos será un almacén donde estén registrados todos los comandos de manera que un cliente pueda seleccionar un comando específico.

Para crearlo, definimos una interfaz que cumplirá el manager:

export interface ICommandManagerService {
    getCommand: (commandName: CommandName) => ICommandService | undefined;
}

Básicamente tendrá un método que devolverá un comando en base a su nombre. Los comandos se inyectarán en el constructor del manager y se guardarán en un array, quedando la implementación de la siguiente manera:

export class CommandManagerService implements ICommandManagerService {
 
  private _commands: Array<ICommandService> = [];
 
  public constructor(
    _createHeaderCommandService: CreateHeaderCommandService,
    _modifyTotalAmountCommandService: ModifyTotalAmountCommandService) {
    this._commands = [
        _createHeaderCommandService,
        _modifyTotalAmountCommandService
    ];
  }
 
  public getCommand = (commandName: CommandName): ICommandService | undefined => 
    this._commands.find((command: ICommandService)=> command.getCommandName() === commandName);
 
}

Cliente

Ahora ya solo queda modificar el cliente para ejecutar el comando seleccionado con el mánager y dárselo al invoker:

public async totalAmountBlur($event: UIEvent): Promise<void> {
    const command: ICommandService | undefined = this._commandManagerService.getCommand(CommandName.ModifyTotalAmount);
    if (!command) {
      throw new Error('No command found!');
    }
    this._invoiceInvokerService.setCommand(command);
    await this._invoiceInvokerService.invoke();
  }

En este último caso, si tenemos varias acciones como por ejemplo cambiar el número de factura, su fecha, etc. se podría estar repitiendo este código para ejecutar el comando.

Para subsanar esta repetición de código, crearemos un nuevo invoker para facturas heredando del actual que realizará la selección del comando y su ejecución.

Invoice Invoker

Realizará la selección del comando y su ejecución en base a un método parametrizado para recibir qué comando debe ejecutar.

Para ello crearemos una interfaz heredando de la general con el nuevo método a implementar:

import { IInvokerService } from '../../iInvokerService';
import { CommandName } from '../commands/commands';
 
export interface IInvoiceInvokerService extends IInvokerService {
    invokeInvoiceCommand(commandName: CommandName): Promise<void>;
}

Y su clase heredará también del invoker genérico creando el nuevo método:

export class InvoiceInvokerService extends InvokerService implements IInvoiceInvokerService {
 
  public constructor(private _commandManagerService: CommandManagerService) {
    super();
  }
 
  public async invokeInvoiceCommand(commandName: CommandName): Promise<void> {
    const command: ICommandService | undefined = this._commandManagerService.getCommand(commandName);
    if (!command) {
      throw new Error('No command found!');
    }
    this.setCommand(command);
    await this.invoke();
  }
 
}

Cliente

public async totalAmountBlur($event: UIEvent): Promise<void> {
    await this._invoiceInvokerService.invokeInvoiceCommand(CommandName.ModifyTotalAmount);
  }

Ahora el cliente queda solamente con una única línea de código de manera que se ve más limpio el código del componente y toda la lógica la pasamos a servicios.

Así pues para futuros comandos solamente habría que pasarle el nombre del comando a ejecutar.

Variantes de implementación

A partir de este código también se podría cambiar algunas implementaciones siempre a gusto del programador:

  • Cambiar el enumerado de strings, por un objeto de constantes o devolver un tipo “Type”.
  • El manager bien podría implementar el tipo “Map” en vez de usar un Array para guardar los comandos ó utilizar “Record”, etc.
  • En el invoker específico se podría incluso hacer métodos exclusivos para cada comando, por ejemplo: this._invoiceInvokerService.modifyTotalAmount(), this._invoiceInvokerService.createHeader(), etc.

Conclusiones

Hemos aprendido como a partir del patrón command visto con anterioridad se pueden organizar mejor los comandos así como mejorar su ejecución mediante un invoker mejor adaptado.

Puedes descargar el código del artículo en GitHub

Deja un comentario

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