Mittwoch, 25. August 2010

Rock SOLID oder SOLID Rocks

SOLID ist ein künstliches Kurzwort von Robert C. Martin. SOLID steht für die fünf Basispatterns der objektorientierten Programmierung und Designs. Die Patterns in ihrer Kombination erlauben es wartbare und nachhaltige Software zu schreiben. SOLID wird häufig beim Refactoring eingesetzt um "Code Smells" zu entfernen.In der Designphase hingegen wird SOLID beim Klassendesign als Richtlinie verwendet.

Single Responsibility Principle (SRP)

Eine Klasse sollte nur eine einzige Zuständigkeit und damit verbunden auch nur einen einzigen Änderungsgrund haben. Robert C. Martin hat diesen Term basierend auf dem Prinzip der Kohäsion von Tom DeMarco formuliert und beschrieben.

Die Kohäsion einer Klasse ist ein interessanter Aspekt und bedarf näherer Betrachtung. Eine Klasse besitzt eine hohe Kohäsion durch einen hohen Grad der Spezialisierung. Monsterklassen, denen man sporadisch begegnet, sind oftmals zur zentralen Schnittstelle mit wenig Funktionalität und häufig nur zum Lookup von Ressourcen geworden.

Monsterklassen, die als Vermittler und Verteiler zum Ansprechen unterschiedlichster Ressourcen fungieren, sind definitiv mit schwacher Kohäsion ausgestattet. Diese Klassen arbeiten perfekt in ihrer gewohnten Umgebung , weil die Menge der Funktionalitäten leicht vom Entwickler genutzt werden kann. Im Zuge der Weiterentwicklung, Modularisierung und Wiederverwendung sind solche Klassen allerdings grauenvoll und bei entsprechender Komplexität kaum zu refaktorisieren. Das unüberschaubare Netz dieser Klassen breitet sich von einer zentralen Stelle bis in den letzten Winkel eines Softwareproduktes aus.

Die Kohäsion einer Klasse bezieht sich nicht nur auf die reinen Funktionalitäten in Form von Methoden, sondern auch auf die Objektdaten ausgedrückt durch Attribute. Übel sind schwach kohesive Datenstrukturen, die gegen das Prinzip der Kapselung verstoßen und deswegen auch noch öffentlich zugänglich sind.

Open Closed Principle (OCP)

Eine Klasse sollte offen für Erweiterungen, aber geschlossen für Modifikationen sein. In der OOP wird dieses Prinzip durch Vererbung und Polymorphie erfüllt. Das Prinzip ist von Dr. Bertrand Meyer in seinem Buch Object Oriented Software Construction formuliert und publiziert worden..

Die Grundidee des Prinzips ist, dass sobald eine Klasse fertiggestellt ist, diese nur noch im Fehlerfall korrigiert wird. Neue und zu ändernde Funktionalitäten sind in einer abgeleiteten Klasse zu deklarieren. Abgeleitete Klassen nutzen dabei die Funktionalitäten der Basisklassen durch Vererbung wodurch die saubere Wiederverwendung von Quellcode ohne Duplizierung erreicht wird.

Die Ableitung von Klassen und das Nutzen der objektorientierten Möglichkeiten durch Patterns wie beispielsweise Template Method und Hooks erlaubt es, dass nicht nur die abgeleitete Klasse Funktionalitäten der Basisklasse verwendet, sondern auch das die Basisklasse Funktionalitäten nutzt, die erst in der abgeleiteten Klasse formuliert werden.

Exemplarisches Beispiel für Template Method:

abstract class TemplateMethod {

    final void calculate() {
   
        abstractOperation();
        operation();       
        hook();
    }
   
    /*
     * overridden in subclass
     */
    abstract void abstractOperation();
   
    void operation() {
        // implementation
    }
   
    void hook() {
        // implementation in subclass
    }
}

public class ConcreteTemplateMethod extends TemplateMethod {

    @Override
    void abstractOperation() {
        // implementation
    }

    @Override
    void hook() {
        // implementation of the hook
    }
}

Liskov Substitution Principle (LSP)

Jede Erweiterung einer Klasse soll die Basisklasse vollständig ersetzen. Eine abgeleitete Klasse darf sich nicht anders als seine Basisklasse verhalten und in keinem Fall die Sicht auf die Basisklasse einschränken oder gar verdecken. Das Prinzip wurde von Barbara Liskov und Jeannette Wing formuliert. In diesem Prinzip spielt die Erwartungshaltung eine Rolle. Grundsätzlich sollte sich eine Klasse so verhalten wie ein Entwickler das erwarten würde. Im Zuge von Vererbungshierarchien kann es dabei unter Umständen zu Abweichungen kommen.

Ein einfaches Beispiel ist die Fehlerbehandlung. Wirft eine Methode der Basisklasse eine überprüfte Ausnahme, muss die überschreibende Methode in der abgeleiteten Klasse dieselbe Ausnahme werfen. In manchen Fällen ist das Liskov Prinzip nur sehr schwer einzuhalten, sodass anstelle von Vererbung die Delegierung angewendet wird.

Das Delegieren trägt zur losen Kopplung bei, lässt Klassen leicht verständlich erscheinen, fördert die Erweiterbarkeit und minimiert Seiteneffekte. Häufig sind lose gekoppelte Klassen die zwingende Voraussetzung für TDD.

 Exemplarisches Beispiel für eine Delegierung:

interface IToDelegate {

    public void delegateMethod();
}

class ToDelegate implements IToDelegate {
   
    public void delegateMethod() {
        // implementation
    }
}

public class DelegateTo implements IToDelegate {

    private final IToDelegate delegate = new ToDelegate();
   
    @Override
    public void delegateMethod() {
        delegate.delegateMethod();       
    }
}

Interface Segregation Principle (ISP)

In einer Schnittstelle sollen nur die Methoden eingefügt werden, die auch tatsächlich Verwendung finden. Überflüssige Methoden verkomplizieren und überladen eine Schnittstelle. Das Prinzip ist von Robert C. Martin formuliert worden. Wesentlich ist das öffentliche Schnittstellen nach ihrer Publizierung nicht mehr verändert werden dürfen, sodass die Menge von Methoden, die nicht verwendet werden, für alle Ewigkeit in der öffentlichen Schnittstelle verbleiben.

Schnittstellen sollten mit Bedacht implementiert werden. Es ist nicht zielführend die Schnittstelle einer Collection-Klasse zu implementieren, wenn dabei viele Methoden implementiert werden müssen, für die es eigentlich keinen Verwender gibt.

Es ist ratsam Schnittstellen nicht grundsätzlich zu verwenden. Die minimale Voraussetzung einer Schnittstelle sollte sein, dass es zwei unterschiedliche Implementierungen für die Schnittstelle gibt.

Dependency Inversion Principle (DIP)

Das Abhängigkeitsinversionsprinzip schreibt vor, das Klassen möglichst nicht von konkreten Implementierungen anderer Klassen, sondern von deren Schnittstellen abhängig sein sollen. Der Vorteil dabei ist, dass man die Implementierung bei Bedarf ändern kann ohne einen Seiteneffekt hervorzurufen, der durch viele undurchschaubare Abhängigkeiten entstehen kann. Dependency Inversion fördert deshalb deutlich die Wartbarkeit einer Software.

Robert C. Martin hat das Dependency Inversion Principle in zahlreichen Publikationen beschrieben, sodass es heute vielfältig Anwendung findet. Dependency Inversion führt zu lose gekoppelten Komponenten. Module eines höheren Levels sollen dabei nicht von Moduln eines niedrigeren Levels anbhängig sein. Abstraktionen sollen nicht von Details abhängen und umgekehrt sollen Details nicht von einer Abstraktion abhängig sein.

Dependency Inversion drückt sich auch im Adapter-Pattern aus, welches sich sehr gut zur Adaption unterschiedlicher Schnittstellen eignet.

Exemplarisches Beispiel für einen Adapter:

interface Enumeration {

    public boolean hasMoreElements();
    public Object nextElement();
}

interface Iterator {

    public boolean hasNext();
    public Object next();
    public void remove();
}

public final class EnumerationIteratorAdapter implements Iterator {

    private final Enumeration enumeration;
       
    public EnumerationIteratorAdapter(final Enumeration enumeration) {   
        this.enumeration = enumeration;
    }

    @Override
    public boolean hasNext() {       
        return(enumeration.hasMoreElements());
    }

    @Override
    public Object next() {
        return(enumeration.nextElement());
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException();
    }
}


Der Rechtshinweis des Java Blog für Clean Code Developer ist bei der Verwendung und Weiterentwicklung des Quellcodes des Blogeintrages zu beachten.