Kontakt
Telefon +49 2161 17 58 83
Mobil +49 179 72 66 112
E-Mail info@ulrich-borchers.de

SOLID: Ein Überblick

Das Kunstwort SOLID benennt wesentliche Grundprinzipien der objektorientierten Softwareentwicklung, die zu besserer Software führen, wenn man Ihnen folgt.

Unter anderem die Design Patterns der GoF richten sich nach diesen Prinzipien. Es sind jedoch ganz allgemein fünf wesentliche Prinzipien der objektorientierten Entwicklung, deren Berücksichtigung etliche Vorteile bringt.

Neben einer kurzen Beschreibung des jeweiligen Prinzips möchte ich hier Beispiele in PHP zeigen, welche die Anwendung dieser Prinzipien deutlich machen.

Die SOLID-Prinzipien sind weniger ein akademisches Thema, sondern beschreiben vielmehr auf Erfahrung basierende, handfeste Kriterien des praktischen Software-Designs in der täglichen Arbeit, die einen nicht zu unterschätzenden Einfluss auf Entwicklungskosten und -möglichkeiten haben, und deren Berücksichtigung entscheidend dafür ist, wie gut sich eine Software implementieren, langfristig weiterentwickeln und später warten lässt.

Um den praktischen Nutzen der Anwendung dieser Prinzipien zu zeigen bemühe ich mich nachfolgend um Code, der, wenn auch vereinfacht, aus dem konkreten Entwicklungsalltag stammen könnte.

"D": Dependency Inversion

Das "Abhängigkeits-Umkehrungs-Prinzip" besagt im Wesentlichen, dass Klassen so gestaltet werden, dass sie von Abstraktionen abhängen und nicht von konkreten Implementierungen. Außerdem sollen Klassen auf demselben Abstraktionsniveau verbunden sein.

Ein Beispiel:

abstract class Reader
{
  abstract public function readLine();
  
  final public function readAll()
  {
    $lines = [];
    
    while (true == $line = $this->readLine()) {
    
      $lines[] = $line;
    }
    
    return $lines;
  }
}

class ScreenPrinter
{
  public function printFromReader(Reader $reader)
  {
    while ($line = $reader->readLine()) {
    
      echo $line, "\n";
    }
  }
}

Reader beschreibt einen Datentyp, der zeilenweise und zudem alle Zeilen auf einmal lesen kann, unabhängig davon, wie das realisiert ist.

Damit ist die Methode ScreenPrinter::printFromReader() vom Basistyp Reader abhängig, der konkret implementiert wird, wobei beliebige Varianten möglich sind: Es entsteht die Möglichkeit, das Lesen technisch unterschiedlich auszugestalten.

Eine Variante eines Reader ist das Lesen einer Datei:

class FileReader extends Reader
{
  protected $fp;

  public function __construct($filename)
  {
    $this->fp = fopen($filename, 'r');
  }
  
  public function readLine()
  {
    return fgets($this->fp);
  }
  
  public function __destruct()
  {
    fclose($this->fp);
  }
}

ScreenPrinter kann somit den Inhalt einer Datei ausgeben:

$printer = new ScreenPrinter();

$printer->printFromReader(new FileReader('/tmp/uploads/readme.txt'));

Ein ScreenPrinter kann genauso einen spezialisierteren Reader verwenden:

class MessageReader extends FileReader
{
  public function __construct()
  {
    parent::__construct('/var/log/messages');
  }
}
$printer = new ScreenPrinter();

$printer->printFromReader(new MessageReader());

Zu sehen ist hier, dass die Programmierung gegen den Basistyp Reader in printFromReader(), Möglichkeiten erschließt, die bei Verwendung eines spezielleren Datentyps nicht vorhanden wären.

Wir gehen noch weiter und schreiben einen Reader, der den Response eines API-Calls ausgibt. Ein API-Service hat in diesem Beispiel immer die Methode fetchLines():

interface APIService
{
  /**
   * @return array
   */
  public function fetchLines($name);
}

Das existiert als Implementierung mit Zugriff auf einen SOAP-Service:

class SOAPAPIService implements APIService
{
	public function __construct($endpoint, array $credentials = [])
	{
	  //...
	}
	
	public function fetchLines()
	{
	  //...
	}
}

APIService definiert die Schnittstelle, über welche die Daten einer entfernten API abgerufen werden. SOAPAPIService implementiert den Zugriff auf eine SOAP-API, stellt also mit anderen Worten eine SOAP-basierte Implementierung von fetchLines() bereit.

Damit ließe sich dann ein API-basierter Reader schreiben:

class APIServiceReader extends Reader
{
  protected $lines = [];

  public function __construct(APIService $service)
  {
    $this->lines = $service->fetchLines();
  }
  
  public function readLine()
  {
    return array_shift($this->lines);
  }
}

Diesen können wir ebenfalls dem ScreenPrinter geben:

$printer = new ScreenPrinter();

$printer->printFromReader(new APIServiceReader('http://www.example.com/api/logfile'));

Anhand dieses Beispiels sehen Sie, dass die Umkehrung der Abhängigkeiten bedeutet, gegen Basistypen zu programmieren - hier ist es der Typ Reader.

Da die Implementierung des ScreenPrinter von diesem Basistyp abhängig ist (umgekehrt zur Abhängigkeit von konkreten Typen am Ende der Vererbungshierarchie), entstehen Flexibilität und Erweiterungsmöglichkeiten, die es sonst nicht gäbe.

Das gezeigte Beispiel ist bis hierhin nicht ganz perfekt, da zwei unterschiedliche Abstraktionsebenen vermischt werden. Besser, um auch das Kriterium zu erfüllen, dass Abhängigkeiten nur auf derselben Abstraktionsebene bestehen, ist diese Lösung:

abstract class Printer
{
  abstract protected printLine($line);

  public function printFromReader(Reader $reader)
  {
    while ($line = $reader->readLine()) {
    
      $this->printLine($line);
    }
  }
}

class ScreenPrinter extends Printer
{
  protected printLine($line)
  {
    echo $line, "\n";
  }
}

Nun sind mithilfe der Template-Methode printLine() die jeweils abstrakten Typen Printer und Reader auf derselben Abstraktionsebene voneinander abhängig.

Dadurch hat bei diesem einfachen Beispiel der Gewinn an Verständlichkeit zwar nicht zugenommen, jedoch sind Flexibilität und damit Mächtigkeit der Lösung deutlich gesteigert worden, da nun beliebige Printer mit beliebigen Readern kombinierbar sind und dadurch außerdem beide Typen überhaupt erst unabhängig voneinander entwickelt werden können.

Dieser letzte Vorteil resultiert aus der konsequenten Anwedung des "Abhängigkeits-Umkehrungs-Prinzips".

Zurück

Hands On PHP

Harter Hut

Programmiertes - Jenseits von Prosa.

Zend Certified Engineer
Zend Certified Engineer ZF
Oracle Certified Professional, MySQL 5.6 Developer
Sun Certified Java Programmer (SCJP)
Sun Certified Web Component Developer (SCWCD)
RSS
tl_files/open_clip_art/symbole/rss-icon.png  Abonnieren