Az szoftverfejlesztésben a Parancs (Command) minta egy objektumorientált viselkedési minta, melynél egy adott fogadó (Receiver) objektumra történő, a paraméterekkel és minden egyéb függőséggel együtt megadott metódushívást (akció, Action) egy parancs (Command) objektumba zárunk (későbbi meghívás céljából). A parancs objektum tartalmaz egy egyszerű, általában paraméter nélküli publikus metódust (például execute
), ennek hívásakor kerül végrehajtásra az akció. A parancs objektum így átadható egy hívó (Invoker) objektumnak, melynek csak az a feladata, hogy (akár szolgaian, akár valamilyen stratégia szerint) végrehajtsa. A parancsokat általában egy vagy több ügyfél (Client) objektum állítja össze és adogatja át a hívónak.
A parancs minta megkönnyíti a generikus komponensek létrehozását. Az ügyfélnek nem kell tudnia arról, hogy a hívó milyen stratégiával és kiegészítésekkel kezeli a végrehajtásokat. A hívónak pedig nem kell ismernie a parancsban lévő hívás részleteit.
A parancs objektumok az implementáláshoz hasznosak.
Swing és Borland Delphi programozási nyelvekben, az „Action” (művelet) egy parancs objektum. Amellett, hogy képes végrehajtani a kívánt parancsot, egy „Action” lehet egy társított ikon, egy billentyűzet gyorsbillentyű, tooltip, és így tovább. Egy eszköztár gomb, vagy egy menüpont összetevője létrejöhet kizárólag egy „Action” vezérlése végett.
Ha minden felhasználói műveletet (Action) egy parancs objektum reprezentál, akkor a program képes rögzíteni egy összetett művelet lépéseit (Macro) azáltal, hogy egy listába gyűjti a végrehajtott parancs objektumokat. Később ezeket az eredeti sorrendben végighívva újrajátszhatja a műveletsort.
Ha a program "Script" motort is tartalmaz, minden parancs objektum implementálhat egy toScript() metódust, és a felhasználói műveletek akkor könnyebben rögzítve lehetnek "Script"-ként.
Olyan nyelveket használva, mint például Java, ahol a kód "Stream"-elhető egy bizonyos távoli helyről egy másikra "URLClassloader"-ek és "Codebase"-k segítségével, ott a parancsok lehetővé tesznek egy újfajta viselkedéseket, a távoli helyeken. (EJB Command, Master Worker)
A parancs objektum egy gyakori bővített változata, amikor a normál akció mellett egy visszavonó (undo) akciót is tárol, melynek meghívására az állapot visszaáll a normál akció meghívása előttire. Előfordulhat, hogy ez az undo akció késleltetett kiértékelésű, főleg ha csak a művelet végrehajtása után állapítható meg, hogy hogyan kell visszavonni (például random vagy a fogadó állapotától függő műveleteknél). Ha minden felhasználói művelet ilyen típusú parancs objektumként van implementálva, és ezeket (vagy legalább a legutóbbiakat) egy veremben tárolja a program, akkor sorra vissza is lehet őket vonni. Amikor a felhasználó a visszavonást kezdeményezi, lekérésre kerül a veremből a legutóbbi parancs objektum, amelyen rögtön végre kell hajtani az `undo` metódust. Ennek sikeres végrehajtása után a parancs kikerül a veremből (illetve opcionálisan átkerül egy redo verembe).
Lehetőség van rengeteg parancs objektum küldésére a hálózaton keresztül, hogy végrehajtsuk azt egy másik számítógépen.
Itt a parancsok egy-egy feladatként (task) vannak megírva egy megosztott erőforrás részére, és potenciálisan több szálon, párhuzamosan lesznek végrehajtva.
Feltételezzük, hogy egy program parancsok soraiból épül fel melyek sorban hajtódnak végre. Parancs objektumonként van egy getEstimatedDuration() metódusunk, mellyel, a program könnyen megbecsüli annak teljes hosszát (időtartamát). Ez tükröződhet egy folyamatjelző által, mely észszerűen mutatja, hogy milyen közel van a program ahhoz, hogy végezzen az összes feladattal.
Egy tipikus, általános célú szálcsoport osztálynak rendelkeznie kell egy publikus addTask() metódussal, amely hozzáad egy munkaeszközt egy belső feladat sorához (queue-hoz) várván a feladat befejezésére. Ez azt jelenti, hogy a szálcsoport meghívja a parancsokat a (queue-ból) sorból. A sor elemei parancs objektumok. Általában ezek az objektumok egy közös interfészt (common interface-t) implementálnak, mint például „java.lang.Runnable” amely lehetőséget nyújt a szálcsoportoknak, hogy végrehajtsák a parancsokat még akkor is, ha maga a szálcsoport osztály úgy van megírva, hogy nincs ismerete a konkrét feladatokról.
Hasonlóan a visszavonáshoz, egy adatbázis motor, vagy egy szoftver telepítő tartalmazhat egy listát a műveletekről, amely végre lettek, vagy végre lesznek hajtva. Amennyiben valamelyikük nem sikerül, az összes többi visszafordítható vagy eldobható (általában „rollback”-nek hívják). Például, ha van két adatbázis tábla, melyek hivatkoznak egymásra, amiket frissíteni kell, és a második frissítés nem sikerül, a tranzakció visszaállítható, így az első tábla nem tartalmaz majd hibás hivatkozásokat.
Gyakran egy varázsló több oldalnyi konfigurációs lapot mutat be egyetlen művelet érdekében, mely csak akkor következik be, ha a felhasználó rákattint a „Kész” gombra az utolsó oldalon. Ezekben az esetekben a természetes út a felhasználói interfész kódjának és az alkalmazás kódjának szétválasztása érdekében, hogy implementáljuk a varázslót parancs objektum használatával. A parancs objektum akkor jön létre, amikor a varázsló először megjelenik. A varázsló minden oldalában tárolja a GUI változásokat a parancs objektumban, tehát az objektum annyira előrehaladott (belakott), amennyire a felhasználó halad. A „Kész” gomb egyszerűen elindít egy „execute()” hívást. Ezen a ponton kezdődik a parancs osztály munkája, a parancsok sorozatának végrehajtása.
A terminológiát arra használjuk, hogy leírjunk, a parancs tervezési minta implementációk nem következetesek és ennél fogva zavaró lehet. Ez a kétértelműség eredménye, a szinonimák és implementációk elfedhetik az eredeti mintát.
1. Kétértelműség
2. Szinonimák és homonimák
3. Implementációk, melyek már túlmutatnak az eredeti parancs tervezési mintán
Tekintsünk egy egyszerű kapcsolót, legyen a neve „Switch”. Ebben a példában konfigurálunk egy "Switch"-et két paranccsal: ez a kettő nem más mint, „kapcsoljuk LE” a lámpát, és „kapcsoljuk FEL” a lámpát.
Az előnye ennek a különös parancs tervezési minta implementációnak az az, hogy a "Switch" használható bármilyen eszköz által, nem csak a lámpa által. A "switch" a következő példában le és felkapcsolhatja a lámpát, de a "Switch"-ek konstruktora képes elfogadni bármilyen más "Subclass" parancsát aminek két paramétere van. Például úgy is be tudjuk állítani a "Swith"-et, hogy az ki vagy be kapcsoljon egy motort.
A következő kód a „Parancs tervezési minta” egy implementációja C#-ban.
using System;
using System.Collections.Generic;
namespace CommandPattern
{
public interface ICommand
{
void Execute();
}
/* The Invoker class */
public class Switch
{
ICommand _closedCommand;
ICommand _openedCommand;
public Switch(ICommand closedCommand, ICommand openedCommand)
{
_closedCommand = closedCommand;
_openedCommand = openedCommand;
}
//close the circuit/power on
public void Close()
{
_closedCommand.Execute();
}
//open the circuit/power off
public void Open()
{
_openedCommand.Execute();
}
}
/* An interface that defines actions that the receiver can perform */
public interface ISwitchable
{
void PowerOn();
void PowerOff();
}
/* The Receiver class */
public class Light : ISwitchable
{
public void PowerOn()
{
Console.WriteLine("The light is on");
}
public void PowerOff()
{
Console.WriteLine("The light is off");
}
}
/* The Command for turning on the device - ConcreteCommand #1 */
public class CloseSwitchCommand: ICommand
{
private ISwitchable _switchable;
public CloseSwitchCommand(ISwitchable switchable)
{
_switchable = switchable;
}
public void Execute()
{
_switchable.PowerOn();
}
}
/* The Command for turning off the device - ConcreteCommand #2 */
public class OpenSwitchCommand : ICommand
{
private ISwitchable _switchable;
public OpenSwitchCommand(ISwitchable switchable)
{
_switchable = switchable;
}
public void Execute()
{
_switchable.PowerOff();
}
}
/* The test class or client */
internal class Program
{
public static void Main(string[] args)
{
string arg = args.Length > 0 ? args[0].ToUpper() : null;
ISwitchable lamp = new Light();
//Pass reference to the lamp instance to each command
ICommand switchClose = new CloseSwitchCommand(lamp);
ICommand switchOpen = new OpenSwitchCommand(lamp);
//Pass reference to instances of the Command objects to the switch
Switch @switch = new Switch(switchClose, switchOpen);
if (arg == "ON")
{
//Switch (the Invoker) will invoke Execute() (the Command) on the command object - _closedCommand.Execute();
@switch.Close();
}
else if (arg == "OFF")
{
//Switch (the Invoker) will invoke the Execute() (the Command) on the command object - _openedCommand.Execute();
@switch.Open();
}
else
{
Console.WriteLine("Argument \"ON\" or \"OFF\" is required.");
}
}
}
}
Egy egyszerűbb példa:
/*IVSR: Command Pattern*/
using System;
using System.Collections;
using System.Linq;
namespace IVSR.Designpatterns.CommandPattern_demo
{
#region ICommand Interface
interface ICommand
{
string Name { get; set; }
string Description { get; set; }
void Run();
}
#endregion
#region Command Invoker
class CInvoker
{
// Array to hold list of commands
private ArrayList listOfCommands = new ArrayList();
//Default constructor to load commands
public CInvoker()
{
LoadCommands();
}
//Loads the commands to arrylist
private void LoadCommands()
{
listOfCommands.Add(new cmdOpen());
listOfCommands.Add(new cmdClose());
listOfCommands.Add(new cmdCreate());
listOfCommands.Add(new cmdUpdate());
listOfCommands.Add(new cmdRetrieve());
}
//Find command using foreach
public ICommand GetCommand(string name)
{
foreach (var item in listOfCommands)
{
ICommand objCmd = (ICommand)item;
if (objCmd.Name == name)
{
return objCmd; //return the command found
}
}
return null; //return if no commands are found
}
}
#endregion
#region Commands
class cmdOpen : ICommand //command 1
{
private string _name = "open", _description = "opens a file";
public string Name { get { return _name; } set { _name = value; } }
public string Description { get { return _description; } set { _description = value; } }
public void Run() { Console.WriteLine("running open command"); }
}
class cmdClose : ICommand //command 2
{
private string _name = "close", _description = "closes a file";
public string Name { get { return _name; } set { _name = value; } }
public string Description { get { return _description; } set { _description = value; } }
public void Run() { Console.WriteLine("running close command"); }
}
class cmdCreate : ICommand //command 3
{
private string _name = "create", _description = "creates a file";
public string Name { get { return _name; } set { _name = value; } }
public string Description { get { return _description; } set { _description = value; } }
public void Run() { Console.WriteLine("running create command"); }
}
class cmdUpdate : ICommand //Command 4
{
private string _name = "update", _description = "updates a file";
public string Name { get { return _name; } set { _name = value; } }
public string Description { get { return _description; } set { _description = value; } }
public void Run() { Console.WriteLine("running update command"); }
}
class cmdRetrieve : ICommand //command 5
{
private string _name = "retrieve", _description = "retrieves a file";
public string Name { get { return _name; } set { _name = value; } }
public string Description { get { return _description; } set { _description = value; } }
public void Run() { Console.WriteLine("running Retrieve command"); }
}
#endregion
#region MAIN
class Program
{
static void Main(string[] args)
{
//Command pattern example
CInvoker cmdInvoker = new CInvoker();
ICommand cmd1 = cmdInvoker.GetCommand("open");
cmd1.Run();
cmdInvoker.GetCommand("update").Run();
//or
new CInvoker().GetCommand("close").Run();
}
}
#endregion
}
import java.util.List;
import java.util.ArrayList;
/* The Command interface */
public interface Command {
void execute();
}
/* The Invoker class */
public class Switch {
private List<Command> history = new ArrayList<Command>();
public void storeAndExecute(Command cmd) {
this.history.add(cmd); // optional
cmd.execute();
}
}
/* The Receiver class */
public class Light {
public void turnOn() {
System.out.println("The light is on");
}
public void turnOff() {
System.out.println("The light is off");
}
}
/* The Command for turning on the light - ConcreteCommand #1 */
public class FlipUpCommand implements Command {
private Light theLight;
public FlipUpCommand(Light light) {
this.theLight = light;
}
public void execute(){
theLight.turnOn();
}
}
/* The Command for turning off the light - ConcreteCommand #2 */
public class FlipDownCommand implements Command {
private Light theLight;
public FlipDownCommand(Light light) {
this.theLight = light;
}
public void execute() {
theLight.turnOff();
}
}
/* The test class or client */
public class PressSwitch {
public static void main(String[] args){
Light lamp = new Light();
Command switchUp = new FlipUpCommand(lamp);
Command switchDown = new FlipDownCommand(lamp);
Switch mySwitch = new Switch();
switch(args[0]) {
case "ON":
mySwitch.storeAndExecute(switchUp);
break;
case "OFF":
mySwitch.storeAndExecute(switchDown);
break;
default:
System.out.println("Argument \"ON\" or \"OFF\" is required.");
}
}
}
class Switch(object):
"""The INVOKER class"""
@classmethod
def execute(cls, command):
command.execute()
class Command(object):
"""The COMMAND interface"""
def __init__(self, obj):
self._obj = obj
def execute(self):
raise NotImplemented
class TurnOnCommand(Command):
"""The COMMAND for turning on the light"""
def execute(self):
self._obj.turn_on()
class TurnOffCommand(Command):
"""The COMMAND for turning off the light"""
def execute(self):
self._obj.turn_off()
class Light(object):
"""The RECEIVER class"""
def turn_on(self):
print("The light is on")
def turn_off(self):
print("The light is off")
class LightSwitchClient(object):
"""The CLIENT class"""
def __init__(self):
self._lamp = Light()
self._switch = Switch()
def switch(self, cmd):
cmd = cmd.strip().upper()
if cmd == "ON":
Switch.execute(TurnOnCommand(self._lamp))
elif cmd == "OFF":
Switch.execute(TurnOffCommand(self._lamp))
else:
print("Argument 'ON' or 'OFF' is required.")
# Execute if this file is run as a script and not imported as a module
if __name__ == "__main__":
light_switch = LightSwitchClient()
print("Switch ON test.")
light_switch.switch("ON")
print("Switch OFF test.")
light_switch.switch("OFF")
print("Invalid Command test.")
light_switch.switch("****")
/* The Command interface */
trait Command {
def execute()
}
/* The Invoker class */
class Switch {
private var history: List[Command] = Nil
def storeAndExecute(cmd: Command) {
cmd.execute()
this.history :+= cmd
}
}
/* The Receiver class */
class Light {
def turnOn() = println("The light is on")
def turnOff() = println("The light is off")
}
/* The Command for turning on the light - ConcreteCommand #1 */
class FlipUpCommand(theLight: Light) extends Command {
def execute() = theLight.turnOn()
}
/* The Command for turning off the light - ConcreteCommand #2 */
class FlipDownCommand(theLight: Light) extends Command {
def execute() = theLight.turnOff()
}
/* The test class or client */
object PressSwitch {
def main(args: Array[String]) {
val lamp = new Light()
val switchUp = new FlipUpCommand(lamp)
val switchDown = new FlipDownCommand(lamp)
val s = new Switch()
try {
args(0).toUpperCase match {
case "ON" => s.storeAndExecute(switchUp)
case "OFF" => s.storeAndExecute(switchDown)
case _ => println("Argument \"ON\" or \"OFF\" is required.")
}
} catch {
case e: Exception => println("Arguments required.")
}
}
}
/* The Invoker function */
var Switch = function(){
var _commands = [];
this.storeAndExecute = function(command){
_commands.push(command);
command.execute();
}
}
/* The Receiver function */
var Light = function(){
this.turnOn = function(){ console.log ('turn on') };
this.turnOff = function(){ console.log ('turn off') };
}
/* The Command for turning on the light - ConcreteCommand #1 */
var FlipUpCommand = function(light){
this.execute = function() { light.turnOn() };
}
/* The Command for turning off the light - ConcreteCommand #2 */
var FlipDownCommand = function(light){
this.execute = function() { light.turnOff() };
}
var light = new Light();
var switchUp = new FlipUpCommand(light);
var switchDown = new FlipDownCommand(light);
var s = new Switch();
s.storeAndExecute(switchUp);
s.storeAndExecute(switchDown);
Coffescript:
# The Invoker function
class Switch
_commands = []
storeAndExecute: (command) ->
_commands.push(command)
command.execute()
# The Receiver function
class Light
turnOn: ->
console.log ('turn on')
turnOff: ->
console.log ('turn off')
# The Command for turning on the light - ConcreteCommand #1
class FlipUpCommand
constructor: (@light) ->
execute: ->
@light.turnOn()
# The Command for turning off the light - ConcreteCommand #2
class FlipDownCommand
constructor: (@light) ->
execute: ->
@light.turnOff()
light = new Light()
switchUp = new FlipUpCommand(light)
switchDown = new FlipDownCommand(light)
s = new Switch()
s.storeAndExecute(switchUp)
s.storeAndExecute(switchDown)
Ez a szócikk részben vagy egészben a Command Pattern című angol Wikipédia-szócikk ezen változatának fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel. Ez a jelzés csupán a megfogalmazás eredetét és a szerzői jogokat jelzi, nem szolgál a cikkben szereplő információk forrásmegjelöléseként.