Schablonenmethode

Problem

Unterklassen soll es ermöglicht werden, bestimmte Schritte einer Operation zu überschreiben, ohne deren Struktur zu verändern.

Motivation

Lässt sich eine Aufgabe in Einzeloperationen zerlegen, von denen eine oder mehrere unterschiedlich implementiert werden können, so bietet sich das Zusammenfassen der gemeinsamen Operationen in einer Basisklasse an. Die Unterklassen dieser Klassen implementieren ihrerseits nur die Einzelschritte, in denen sie sich voneinander unterscheiden.

Lösung

In der Basisklasse wird das Grundgerüst der Operation in einer als public final deklarierten Methode implementiert.

Muss ein Teilschritt, dessen Implementierung an eine Unterklasse delegiert werden soll, implementiert werden, so wird die entsprechende Methode in der Basisklasse als abstract protected deklariert. Soll die Implementierung hingegen optional sein, so wird die Methode lediglich als protected deklariert und verfügt in der Basisklasse nur über einen leeren Methodenrumpf.

Anwendungsbeispiele

Eine Schablonenmethode wird gerne eingesetzt, um eine Ausgabe in unterschiedlichen Formaten darstellen zu können. Die Klasse PHPUnit2_Extensions_CodeCoverage_Renderer des PHPUnit-Paketes kombiniert das Entwurfsmuster der abstrakten Fabrik (siehe „Abstrakte Fabrik“) mit einer Schablonenmethode und ermöglicht so die Darstellung von Code-Coverage-Informationen in unterschiedlichen Formaten durch entsprechende Unterklassen. Die Methode render() stellt hierbei die Schablone dar und ruft die den Einzelschritten entsprechenden Methoden in der vorgegebenen Reihenfolge auf.

Beispiel 7.8: Die abstrakte Klasse PHPUnit2_Extensions_CodeCoverage_Renderer

<?php
abstract class PHPUnit2_Extensions_CodeCoverage_Renderer {
  protected $codeCoverageInformation;
 
  protected function __construct($codeCoverageInformation) {
    $this->codeCoverageInformation = $codeCoverageInformation;
  }
 
  public function factory($type, $codeCoverageInformation) {
    $class = 'PHPUnit2_Extensions_CodeCoverage_Renderer_' .
             $type;
 
    $source = 'PHPUnit2/Extensions/CodeCoverage/Renderer/' .
              $type . '.php';
 
    if (@require_once($source)) {
        $object = new $class($codeCoverageInformation);
 
      return $object;
    } else {
      throw new Exception(
        sprintf(
          'Could not load class %s.',
          $class
        )
      );
    }
  }
 
  public final function render() {
    $buffer = '';
 
    foreach ($this->codeCoverageInformation as
             $testCaseName => $sourceFiles) {
      $buffer .= $this->startTestCase($testCaseName);
 
      foreach ($sourceFiles as
               $sourceFile => $executedLines) {
        $buffer .= $this->startSourceFile($sourceFile);
 
        $buffer .= $this->renderSourceFile(
          file($sourceFile),
          $executedLines
        );
 
        $buffer .= $this->endSourceFile($sourceFile);
      }
 
      $buffer .= $this->endTestCase($testCaseName);
    }
 
    return $buffer;
  }
 
  protected function startTestCase($testCaseName) {
  }
 
  protected function endTestCase($testCaseName) {
  }
 
  protected function startSourceFile($sourceFile) {
  }
 
  protected function endSourceFile($sourceFile) {
  }
 
  abstract protected function
  renderSourceFile($codeLines, $executedLines);
}
?>


Beispiel 7.9: Die Klasse PHPUnit2_Extensions_CodeCoverage_Renderer_Text

<?php
require_once 'PHPUnit2/Util/CodeCoverage/Renderer.php';
 
class PHPUnit2_Extensions_CodeCoverage_Renderer_Text
extends PHPUnit2_Extensions_CodeCoverage_Renderer {
  protected function startTestCase($testCaseName) {
    return $testCaseName . "\n\n";
  }
 
  protected function endTestCase($testCaseName) {
    return "\n";
  }
 
  protected function startSourceFile($sourceFile) {
    return '  ' . $sourceFile . "\n\n";
  }
 
  protected function endSourceFile($sourceFile) {
    return "\n";
  }
 
  protected function renderSourceFile($codeLines, $executedLines) {
    $buffer = '';
    $line   = 1;
 
    foreach ($codeLines as $codeLine) {
      $buffer .= sprintf(
        '    %4u|%4s| %s',
 
        $line,
        (isset($executedLines[$line])) ? $executedLines[$line] . 'x' : '',
        $codeLine
      );
 
      $line++;
    }
 
    return $buffer;
  }
}
?>