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