Problem
Objekte sollen dynamisch um Funktionalität erweitert werden können, ohne die zugehörige Klasse durch Unterklassenbildung statisch erweitern zu müssen.
Motivation
Oft kommt es vor, dass man die Funktionalität eines Objektes dynamisch und transparent erweitern oder verändern möchte. Dem statischen Ansatz der Erweiterung der Klassenhierarchie um entsprechende Unterklassen ist meist eine objektbasierte Lösung vorzuziehen. Hierbei kann die Funktionalität verschiedener Objekte durch lose Kopplung zur Laufzeit "zusammengesteckt" werden.
Lösung
Die erweiterte oder veränderte Funktionalität wird in einem so genannten Dekorierer-Objekt modelliert, das dieselbe Schnittstelle wie das zu dekorierende Objekt anbietet. Dies erlaubt die transparente Benutzung des Dekorierer-Objekts durch Verwender des ursprünglichen Objektes. Das Dekorierer-Objekt leitet Methodenaufrufe zur Ausführung an das aggregierte, zu dekorierende Objekt weiter und kann seine zusätzliche Funktionalität vor oder nach diesem Methodenaufruf ausführen.
Anwendungsbeispiele
In Kapitel 3 haben wir mit den Klassen
FilterIterator und LimitIterator bereits
zwei Dekorierer kennen gelernt. Objekte dieser Iterator-Klassen dekorieren
ein anderes Iterator-Objekt und filtern oder limitieren dessen Elemente.
Für die Anpassung und Erweiterung der Testausführung bietet PHPUnit die
Dekorierung der Klasse PHPUnit2_Framework_TestCase an. Das
Grundgerüst des entsprechenden Dekorierers liegt in Form der Klasse
PHPUnit2_Extensions_TestDecorator vor. Diese implementiert
die vom PHPUnit-Framework für die Testausführung benötigte Schnittstelle
PHPUnit2_Framework_Test und stellt ebenso wie die Klasse
PHPUnit2_Framework_TestCase die Zusicherungsmethoden der
Klasse PHPUnit2_Framework_Assert bereit. Sie kann daher
von jedem Verwender, der eigentlich ein Objekt der Klasse
PHPUnit2_Framework_TestCase erwartet, verarbeitet werden.
Beispiel 6.1: Die Klasse PHPUnit2_Extensions_TestDecorator
<?php
require_once 'PHPUnit2/Framework/Assert.php';
require_once 'PHPUnit2/Framework/Test.php';
require_once 'PHPUnit2/Framework/TestResult.php';
class PHPUnit2_Extensions_TestDecorator
extends PHPUnit2_Framework_Assert
implements PHPUnit2_Framework_Test {
protected $test = NULL;
public function
__construct(PHPUnit2_Framework_Test $test) {
$this->test = $test;
}
public function toString() {
return $this->test->toString();
}
public function
basicRun(PHPUnit2_Framework_TestResult $result) {
$this->test->run($result);
}
public function countTestCases() {
return $this->test->countTestCases();
}
protected function createResult() {
return new PHPUnit2_Framework_TestResult;
}
public function getTest() {
return $this->test;
}
public function
run(PHPUnit2_Framework_TestResult $result) {
$this->basicRun($result);
return $result;
}
}
?>Beispiel 6.2: Die Klasse PHPUnit2_Extensions_RepeatedTest
<?php
require_once 'PHPUnit2/Framework/Test.php';
require_once 'PHPUnit2/Framework/TestResult.php';
require_once 'PHPUnit2/Extensions/TestDecorator.php';
class PHPUnit2_Extensions_RepeatedTest
extends PHPUnit2_Extensions_TestDecorator {
private $timesRepeat = 1;
public function
__construct(PHPUnit2_Framework_Test $test,
$timesRepeat = 1) {
parent::__construct($test);
if (is_integer($timesRepeat) &&
$timesRepeat >= 0) {
$this->timesRepeat = $timesRepeat;
} else {
throw new Exception('Illegal argument.');
}
}
public function countTestCases() {
return $this->timesRepeat *
$this->test->countTestCases();
}
public function
run(PHPUnit2_Framework_TestResult $result) {
for ($i = 0;
$i < $this->timesRepeat && !$result->shouldStop();
$i++) {
$this->test->run($result);
}
return $result;
}
}
?>
Für die dekorierte Ausführung eines Tests, in unserem Beispiel also die
wiederholte Ausführung einer Testfallmethode, wird zunächst ein Objekt
der Testfallklasse erzeugt. Dieses wird anschließend dem Konstruktor
der Dekorierer-Klasse (hier PHPUnit2_Extensions_RepeatedTest)
übergeben, um ein entsprechendes Dekorierer-Objekt zu erzeugen, das
dann für die Testausführung mit PHPUnit2_Framework_TestResult
verwendet werden kann.
Beispiel 6.3: Wiederholte Testausführung mit PHPUnit2_Extensions_RepeatedTest
<?php
require_once 'PHPUnit2/Framework/TestResult.php';
require_once 'PHPUnit2/Extensions/RepeatedTest.php';
require_once 'SampleTest.php';
$result = new PHPUnit2_Framework_TestResult;
// Führt SampleTest::testDoSomething() einmal aus.
$test = new SampleTest('testDoSomething');
$result->run($test);
// Führt SampleTest::testDoSomething() einmal aus.
$decoratedTest = new PHPUnit2_Extensions_RepeatedTest($test, 2);
$result->run($decoratedTest);
?>