Jun 06

Heute widmen wir uns mal der Problemstellung, dass wir ein Script schreiben müssen, welches verschiedene Aktionen nacheinander ausführen muss, sozusagen ein Batch-Script in PHP. Wir nehmen an, dass wir eine Aktion haben, die etwas installiert, und eine, die etwas deinstalliert. Die Aktionen sollen beliebig kombinierbar sein und mit einem Befehl auszuführen sein.
In diesem Fall hilft uns das Command-Pattern. Hier registrieren wir eine bestimmte Menge von Kommandos auf einem Controller (hier Invoker), welcher diese dann ausführt. Bevor wir an die Implementation gehen, schauen wir uns kurz das UML-Diagramm an:

Um die Commands noch ein wenig an einander anzugleichen könnten wir auch zunächst eine Abstract definieren, anstatt eines Interfaces, und so direkt Methoden (Implementationen nicht Schnittstellen!!) weitervererben, die in allen Commands gleich sein soll. In unserem Fall begnügen wir uns mit den Interfaces.

Widmen wir uns zunächst der Implementation der beiden Interfaces für den Invoker und das Command:

1
2
3
4
5
6
7
8
9
10
11
interface Invoker
{
	public function add ( Command $Action );
	public function remove ( Command $Action );
	public function run();
}
 
interface Command
{
	public function execute();
}

Die Definition der Schnittstellen ist immer wichtig. Denn nur so können wir sicherstellen, dass Klassen, die in dieser Struktur laufen sollen, später auch wirklich hinein passen.
Wie wir sehen bekommt der Invoker zwei Verwaltungsfunktionen (um Commands hinzuzufügen und zu entfernen) und zusätzlich noch eine Methode um alle Commands auszuführen. Das Command bekommt in unserem Fall nur eine execute-Methode. Intern kann natürlich ein Kommando viele Methoden haben, die für die Ausführung nötig sind, jedoch ist für die Struktur nur wichtig, dass die entsprechenden Command Methoden auf jeden Fall implementiert sind.

In unserem Fall müssen wir noch 3 Klassen implementieren:

- den Invoker (Batch)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Batch implements Invoker
{
	protected $Commands = array();
 
	public function add ( Command $Command )
	{
		$this->Commands[] = $Command;
	}
 
	public function remove ( Command $Command )
	{
		$this->Commands = array_diff($this->Commands, array($Command));
	}
 
	public function run()
	{
		echo "Verarbeitung der Anweisungen gestartet ...<br />";
		echo "============================================<br />";
		foreach ( $this->Commands as $Command )
		{
			$Command->execute();
		}
		echo "============================================<br />";
		echo "Verarbeitung der Anweisungen beendet ...<br />";
	}
}

- ein Command (Install)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Install implements Command
{
	protected $name = NULL;
 
	public function __construct ( $name )
	{
		$this->name = $name;
	}
 
	public function execute()
	{
		echo "+" . $this->name . ' wurde installiert<br />';	
	}
}

- ein Command (Uninstall)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class UnInstall implements Command
{
	protected $name = NULL;
 
	public function __construct ( $name )
	{
		$this->name = $name;
	}
 
	public function execute()
	{
		echo "-" . $this->name . ' wurde deinstalliert<br />';	
	}
}

Ich denke die Methoden sollten selbsterklärend sein. Der Einfachheit halber geben diese Kommandos nur aus was sie tun. Beim Instanziieren geben wir ihnen einen Namen mit wie z.B. Db-Treiber. Die wirkliche Funktionalität kann ja bei Bedarf hinzugefügt werden. Mir geht es hier nur um die Verdeutlichung des Musters.

Nun schreiben wir uns noch ein kleines Testscript:

1
2
3
4
5
$Batch = new Batch;
$Batch->add ( new Uninstall ( 'Pear::DB' ) );
$Batch->add ( new Install ( 'Pear::MDB2' ) );
$Batch->add ( new Install ( 'Pear::XmlTree' ) );
$Batch->run();

Hier instaniziieren wir unser Batch-Objekt und übergeben über die add-Methode drei Commands, die dann in die Queue eingereiht werden und dann in dieser Reihenfolge ausgeführt werden.
Wenn wir dieses Script nun laufen lassen, können an der Ausgabe erkennen, dass unsere Batch-Routine erfolgreich durchgelaufen ist:

Verarbeitung der Anweisungen gestartet …
============================================
-Pear::DB wurde deinstalliert
+Pear::MDB2 wurde installiert
+Pear::XmlTree wurde installiert
============================================
Verarbeitung der Anweisungen beendet …

Wie wir nun gesehen haben kann man mit diesem Muster leicht beliebige Befehle kombinieren. Möchte man allerdings, dass die Befehle geschachtelt werden können, also einen Baum darstellen sollen, können wir dazu noch zusätzlich das Composite Pattern einsetzen. Dies wird evtl. nächste Woche in einem gesonderten Eintrag erläutert. Wie man die beiden dann kombiniert ist dann sicherlich aus beiden Einträgen herzuleiten :-) .

Bei Fragen, Anregungen etc. einfach einen Kommentar hinterlassen :-) .

Post to Twitter Post to Delicious Post to Digg Post to Facebook

written by Alexander \\ tags: , , , , ,


Leave a Reply

i3Theme sponsored by Top 10 Web Hosting and Hosting in Colombia