Los patrones de diseño no solo aceleran la fase de diseño de un proyecto orientado a objetos (OO), sino que también aumentan la productividad del equipo de desarrollo y la calidad del software. Un patrón de comando es un patrón de comportamiento de objeto que nos permite lograr un desacoplamiento completo entre el emisor y el receptor. (Un remitente es un objeto que invoca una operación, y un receptor es un objeto que recibe la solicitud para ejecutar una determinada operación. Con el desacoplamiento, el remitente no tiene conocimiento de la Receiver
interfaz de.) El término solicitudaquí se refiere al comando que se va a ejecutar. El patrón Command también nos permite variar cuándo y cómo se cumple una solicitud. Por lo tanto, un patrón de comando nos proporciona flexibilidad y extensibilidad.
En lenguajes de programación como C, los punteros de función se utilizan para eliminar declaraciones de cambio gigantes. (Consulte el "Consejo 30 de Java: polimorfismo y Java" para obtener una descripción más detallada). Dado que Java no tiene punteros de función, podemos usar el patrón Command para implementar devoluciones de llamada. Verá esto en acción en el primer ejemplo de código a continuación, llamado TestCommand.java
.
Los desarrolladores acostumbrados a utilizar punteros de función en otro idioma pueden verse tentados a utilizar los Method
objetos de la API de Reflection de la misma forma. Por ejemplo, en su artículo "Reflexión de Java", Paul Tremblett le muestra cómo usar Reflection para implementar transacciones sin usar declaraciones de cambio. Me he resistido a esta tentación, ya que Sun desaconseja el uso de Reflection API cuando otras herramientas más naturales para el lenguaje de programación Java serán suficientes. (Consulte Recursos para obtener enlaces al artículo de Tremblett y la página del tutorial de Sun's Reflection). Su programa será más fácil de depurar y mantener si no usa Method
objetos. En su lugar, debe definir una interfaz e implementarla en las clases que realizan la acción necesaria.
Por lo tanto, le sugiero que utilice el patrón Command combinado con el mecanismo de enlace y carga dinámica de Java para implementar punteros de función. (Para obtener detalles sobre el mecanismo de enlace y carga dinámica de Java, consulte "The Java Language Environment - A White Paper" de James Gosling y Henry McGilton, que se incluye en Recursos).
Siguiendo la sugerencia anterior, aprovechamos el polimorfismo proporcionado por la aplicación de un patrón de comando para eliminar declaraciones de cambio gigantes, lo que resulta en sistemas extensibles. También aprovechamos los mecanismos de enlace y carga dinámica únicos de Java para construir un sistema dinámico y extensible dinámicamente. Esto se ilustra en el segundo ejemplo de código a continuación, llamado TestTransactionCommand.java
.
El patrón Command convierte la solicitud en sí misma en un objeto. Este objeto puede almacenarse y pasarse como otros objetos. La clave de este patrón es una Command
interfaz, que declara una interfaz para ejecutar operaciones. En su forma más simple, esta interfaz incluye una execute
operación abstracta . Cada Command
clase concreta especifica un par receptor-acción almacenando Receiver
como variable de instancia. Proporciona diferentes implementaciones del execute()
método para invocar la solicitud. El Receiver
tiene los conocimientos necesarios para llevar a cabo la solicitud.
La Figura 1 a continuación muestra la Switch
- una agregación de Command
objetos. Tiene flipUp()
y flipDown()
operaciones en su interfaz. Switch
se llama invocador porque invoca la operación de ejecución en la interfaz de comandos.
El comando concreto LightOnCommand
, implementa el execute
funcionamiento de la interfaz de comandos. Tiene el conocimiento para llamar a la Receiver
operación del objeto apropiado . Actúa como un adaptador en este caso. Por el término adaptador, me refiero a que el Command
objeto concreto es un conector simple, que conecta el Invoker
y el Receiver
con diferentes interfaces.
El cliente crea una instancia del Invoker
, el Receiver
, y los objetos de comando concreto.
La Figura 2, el diagrama de secuencia, muestra las interacciones entre los objetos. Ilustra cómo se Command
desacopla el Invoker
de Receiver
(y la solicitud que realiza). El cliente crea un comando concreto parametrizando su constructor con el apropiado Receiver
. Luego almacena el Command
en formato Invoker
. El Invoker
vuelve a llamar el comando concreto, que tiene el conocimiento para realizar la Action()
operación deseada .
El cliente (programa principal en el listado) crea un Command
objeto concreto y establece su Receiver
. Como Invoker
objeto, Switch
almacena el Command
objeto concreto . El Invoker
emite una petición llamando execute
al Command
objeto. El Command
objeto concreto invoca operaciones en su Receiver
para realizar la solicitud.
La idea clave aquí es que el comando concreto se registre con el Invoker
y el Invoker
lo devuelva, ejecutando el comando en el Receiver
.
Código de ejemplo de patrón de comando
Echemos un vistazo a un ejemplo simple que ilustra el mecanismo de devolución de llamada logrado a través del patrón Command.
El ejemplo muestra ay Fan
a Light
. Nuestro objetivo es desarrollar un objeto Switch
que pueda encender o apagar cualquier objeto. Vemos que el Fan
y el Light
tienen diferentes interfaces, lo que significa que Switch
tiene que ser independiente de la Receiver
interfaz o no tiene conocimiento del código> Interfaz del receptor. Para solucionar este problema, necesitamos parametrizar cada uno de los Switch
s con el comando apropiado. Obviamente, el Switch
conectado al Light
tendrá un comando diferente al Switch
conectado al Fan
. La Command
clase tiene que ser abstracta o una interfaz para que esto funcione.
Cuando Switch
se invoca el constructor de a , se parametriza con el conjunto apropiado de comandos. Los comandos se almacenarán como variables privadas del Switch
.
Cuando se llaman las operaciones flipUp()
y flipDown()
, simplemente harán el comando apropiado para execute( )
. No Switch
tendrán idea de lo que sucede como resultado de execute( )
ser llamado.
TestCommand.java class Fan {public void startRotate () {System.out.println ("El ventilador está girando"); } public void stopRotate () {System.out.println ("El ventilador no gira"); }} class Light {public void turnOn () {System.out.println ("La luz está encendida"); } public void turnOff () {System.out.println ("La luz está apagada"); }} class Switch {comando privado UpCommand, DownCommand; Interruptor público (Comando arriba, Comando abajo) {Comando arriba = Arriba; // comando concreto se registra con el invocador DownCommand = Down; } void flipUp () {// el invocador devuelve el comando concreto, que ejecuta el comando en el UpCommand del receptor. ejecutar (); } void flipDown () {DownCommand. ejecutar (); }} clase LightOnCommand implementa Command {Private Light myLight; Public LightOnCommand (Light L) {myLight = L;} public void execute () {myLight. encender( ); }} clase LightOffCommand implementa Command {Private Light myLight; Public LightOffCommand (Light L) {myLight = L; } public void execute () {myLight. apagar( ); }} la clase FanOnCommand implementa Command {private Fan myFan; FanOnCommand público (Fan F) {myFan = F; } public void execute () {myFan. startRotate (); }} la clase FanOffCommand implementa Command {private Fan myFan; FanOffCommand público (Fan F) {myFan = F; } public void execute () {myFan. stopRotate (); }} Public class TestCommand {public static void main (String [] args) {Light testLight = new Light (); LightOnCommand testLOC = nuevo LightOnCommand (testLight); LightOffCommand testLFC = nuevo LightOffCommand (testLight); Switch testSwitch = nuevo Switch (testLOC, testLFC); testSwitch.flipUp (); testSwitch.flipDown ();Fan testFan = new Fan (); FanOnCommand foc = new FanOnCommand (testFan); FanOffCommand ffc = nuevo FanOffCommand (testFan); Switch ts = new Switch (foc, ffc); ts.flipUp (); ts.flipDown (); }} Command.java public interface Command {public abstract void execute (); }
Observe en el ejemplo de código anterior que el patrón Command desacopla completamente el objeto que invoca la operación - (Switch )
- de los que tienen el conocimiento para realizarla - Light
y Fan
. Esto nos da mucha flexibilidad: el objeto que emite una solicitud debe saber solo cómo emitirla; no necesita saber cómo se llevará a cabo la solicitud.
Patrón de comando para implementar transacciones
Un patrón de comando también se conoce como patrón de acción o transacción. Consideremos un servidor que acepta y procesa transacciones entregadas por clientes a través de una conexión de socket TCP / IP. Estas transacciones constan de un comando, seguido de cero o más argumentos.
Los desarrolladores pueden usar una declaración de cambio con un caso para cada comando. El uso de Switch
declaraciones durante la codificación es una señal de mal diseño durante la fase de diseño de un proyecto orientado a objetos. Los comandos representan una forma orientada a objetos para admitir transacciones y se pueden utilizar para resolver este problema de diseño.
En el código de cliente del programa TestTransactionCommand.java
, todas las solicitudes se encapsulan en el TransactionCommand
objeto genérico . El TransactionCommand
constructor es creado por el cliente y está registrado con CommandManager
. Las solicitudes en cola se pueden ejecutar en diferentes momentos llamando al runCommands()
, lo que nos da mucha flexibilidad. También nos da la capacidad de ensamblar comandos en un comando compuesto. También tengo CommandArgument
, CommandReceiver
y CommandManager
las clases y subclases de TransactionCommand
- a saber AddCommand
y SubtractCommand
. A continuación se muestra una descripción de cada una de estas clases:
CommandArgument
es una clase auxiliar, que almacena los argumentos del comando. Puede reescribirse para simplificar la tarea de pasar un número grande o variable de argumentos de cualquier tipo.CommandReceiver
implementa todos los métodos de procesamiento de comandos y se implementa como un patrón Singleton.CommandManager
es el invocador y es elSwitch
equivalente del ejemplo anterior. Almacena elTransactionCommand
objeto genérico en sumyCommand
variable privada . CuandorunCommands( )
se invoca, llama alexecute( )
delTransactionCommand
objeto apropiado .
En Java, es posible buscar la definición de una clase dada una cadena que contenga su nombre. En el execute ( )
funcionamiento de la TransactionCommand
clase, calculo el nombre de la clase y lo vinculo dinámicamente con el sistema en ejecución, es decir, las clases se cargan sobre la marcha según sea necesario. Utilizo la convención de nomenclatura, el nombre del comando concatenado por la cadena "Comando" como el nombre de la subclase del comando de transacción, de modo que se pueda cargar dinámicamente.
Tenga en cuenta que el Class
objeto devuelto por newInstance( )
debe convertirse al tipo apropiado. Esto significa que la nueva clase tiene que implementar una interfaz o subclase de una clase existente que es conocida por el programa en tiempo de compilación. En este caso, dado que implementamos la Command
interfaz, esto no es un problema.