Dekorierer

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);
?>