Patrón Command
Parte de la sección Programación Orientada a Objetos del Journey de Dart de Coddy — lección 94 de 110.
El patrón Command encapsula una solicitud como un objeto, lo que permite parametrizar clientes con diferentes solicitudes, encolar operaciones o soportar la funcionalidad de deshacer. En lugar de llamar directamente a un método, se envuelve la acción en un objeto de comando que puede ser almacenado, transferido o ejecutado más tarde.
El patrón involucra tres componentes clave: una interfaz Command con un método execute, Concrete Commands que implementan acciones específicas y un Receiver que realiza el trabajo real. Un Invoker opcional activa los comandos sin saber qué hacen:
abstract class Command {
void execute();
}
class Light {
void turnOn() => print('Light is ON');
void turnOff() => print('Light is OFF');
}
class TurnOnCommand implements Command {
final Light light;
TurnOnCommand(this.light);
@override
void execute() => light.turnOn();
}
class TurnOffCommand implements Command {
final Light light;
TurnOffCommand(this.light);
@override
void execute() => light.turnOff();
}
class RemoteControl {
Command? _command;
void setCommand(Command command) {
_command = command;
}
void pressButton() {
_command?.execute();
}
}
void main() {
var light = Light();
var remote = RemoteControl();
remote.setCommand(TurnOnCommand(light));
remote.pressButton(); // Light is ON
remote.setCommand(TurnOffCommand(light));
remote.pressButton(); // Light is OFF
}El RemoteControl no sabe nada sobre luces; simplemente ejecuta cualquier comando que se le asigne. Este desacoplamiento facilita la adición de nuevos comandos sin modificar el invocador. El patrón Command es ideal para implementar sistemas de deshacer/rehacer, colas de tareas o grabación de macros donde se necesita tratar las acciones como objetos de primera clase.
Desafío
Fácil¡Vamos a construir un sistema de comandos para un editor de texto utilizando el patrón Command! Crearás un sistema que encapsula las operaciones de texto como objetos de comando, permitiendo que un editor ejecute diferentes acciones sin conocer los detalles de cada operación. Este es un caso de uso clásico para el patrón Command; piénsalo como un sistema de deshacer/rehacer donde cada acción se almacena como un objeto.
Organizarás tu código en dos archivos:
commands.dart: Crea tu sistema de comandos aquí. Comienza con una clase abstractaCommandque declare un métodoexecute(). Luego, crea una claseTextDocument(el receptor) que contenga un campoString content(inicialmente vacío) y tenga tres métodos:append(String text)- añade texto al final del contenidoclear()- establece el contenido como una cadena vacíashow()- imprimeDocument: [content]
TextDocument:AppendCommand- recibe un documento y texto en su constructor, y añade el texto cuando se ejecutaClearCommand- recibe un documento en su constructor, y lo limpia cuando se ejecutaShowCommand- recibe un documento en su constructor, y muestra su contenido cuando se ejecuta
TextEditor(el invocador) que almacene una lista de comandos. Debe tener un métodoaddCommand(Command command)para encolar comandos, y un métodorunAll()que ejecute todos los comandos encolados en orden.main.dart: Importa tu archivo de comandos y demuestra el patrón Command. Crea unTextDocumenty unTextEditor. Encola los siguientes comandos en orden: appendHello, appendWorld, muestra el documento, append!, muestra de nuevo, limpia el documento y muestra una última vez. Luego, ejecuta todos los comandos encolados.
Observa cómo el TextEditor no sabe nada sobre añadir, limpiar o mostrar; simplemente ejecuta cualquier comando que se le asigne. ¡Este desacoplamiento es la esencia del patrón Command!
Salida esperada:
Document: Hello World
Document: Hello World!
Document: Hoja de referencia
El patrón Command encapsula una solicitud como un objeto, lo que le permite parametrizar clientes con diferentes solicitudes, encolar operaciones o admitir la funcionalidad de deshacer.
El patrón involucra tres componentes clave:
- Interfaz Command con un método execute
- Comandos Concretos que implementan acciones específicas
- Receiver (Receptor) que realiza el trabajo real
- Invoker (Invocador) (opcional) que activa comandos sin saber qué hacen
Estructura básica:
abstract class Command {
void execute();
}
class Receiver {
void action() => print('Action performed');
}
class ConcreteCommand implements Command {
final Receiver receiver;
ConcreteCommand(this.receiver);
@override
void execute() => receiver.action();
}
class Invoker {
Command? _command;
void setCommand(Command command) {
_command = command;
}
void trigger() {
_command?.execute();
}
}Ejemplo con un sistema de control de luces:
class Light {
void turnOn() => print('Light is ON');
void turnOff() => print('Light is OFF');
}
class TurnOnCommand implements Command {
final Light light;
TurnOnCommand(this.light);
@override
void execute() => light.turnOn();
}
class RemoteControl {
Command? _command;
void setCommand(Command command) {
_command = command;
}
void pressButton() {
_command?.execute();
}
}
void main() {
var light = Light();
var remote = RemoteControl();
remote.setCommand(TurnOnCommand(light));
remote.pressButton(); // Light is ON
}El invocador no sabe nada sobre el receptor; simplemente ejecuta cualquier comando que se le asigne. Este desacoplamiento facilita la adición de nuevos comandos sin modificar el invocador.
Pruébalo tú mismo
import 'commands.dart';
void main() {
// TODO: Crear una instancia de TextDocument
// TODO: Crear una instancia de TextEditor
// TODO: Poner en cola los siguientes comandos en orden:
// 1. Añadir "Hello"
// 2. Añadir " World"
// 3. Mostrar el documento
// 4. Añadir "!"
// 5. Mostrar el documento de nuevo
// 6. Limpiar el documento
// 7. Mostrar el documento una última vez
// TODO: Ejecutar todos los comandos en cola
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de POO
Archivos externosLibrerías e importacionesIntroducción a la POOClases vs. ObjetosLa palabra clave thisMétodosVariables de instanciaConceptos básicos de constructoresResumen - Calculadora sencilla4Seguridad de nulos
Introducción a la seguridad de nulosNulables vs No nulablesLos operadores ? y !Palabra clave Late y Null SafetyOperadores Null-awareNull Safety en clasesResumen - Sistema de perfil de usuario7Clases abstractas e interfaces
Clases abstractasMétodos abstractosInterfaces en DartInterfaces implícitasImplementar vs. ExtenderInterfaces múltiplesRepaso - Calculadora de figuras10Colecciones y Genéricos
Visión general de List, Set y MapColecciones con seguridad de tiposClases genéricasMétodos genéricosRestricciones genéricasIterable e IteratorResumen - Almacenamiento genérico2Constructores en Dart
Constructor por defectoConstructores nombradosListas de inicializaciónConstructores constantesConstructores de fábricaConstructores de redireccionamientoResumen - Constructor de formas5Encapsulamiento
Miembros públicos vs. privadosLa convención del prefijo _Privacidad a nivel de libreríaProfundidad en Getters y SettersOcultamiento de informaciónResumen - Registros de estudiantes8Mixins
Introducción a los MixinsCreación de MixinsUso de múltiples MixinsPalabra clave on en MixinsMixin vs HerenciaMixin vs InterfazResumen - Sistema Animal11Métodos especiales
Sobrescritura de toString()Sobrescritura de hashCode y ==Interfaz ComparableMétodo call()Sobrescritura de noSuchMethodResumen - Colección personalizada14Patrones de diseño Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy3Propiedades de clase
Miembros de instancia vs. estáticosCampos Final y ConstVariables LateMétodos y campos estáticosGetters y SettersResumen - Gestor de cuentas bancarias6Herencia
Herencia básicaLa palabra clave superSobrescritura de métodosLa anotación @overrideLa palabra clave final en clasesConstructores y herenciaResumen - Jerarquía de empleados9Polimorfismo
Fundamentos del polimorfismoPolimorfismo mediante interfacesComprobación de tipos en tiempo de ejecuciónLos operadores is y asPalabra clave covariantResumen - Procesador de pagos12POO Asíncrona
Futures y async/awaitFundamentos de StreamsStream ControllersConstructores asíncronosAsincronía en métodos de claseRepaso - Data Fetcher15Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Repository