Montag, 25. Oktober 2010

Thinking about strategies

Es entstehen immer wieder Debatten bezüglich neuer Programmiersprachenkonstrukte. Closures für Java 7 werden von der Java Community gefordert und schon über einen langen Zeitraum hinweg diskutiert. In Java sind allerdings bereits Funktionsobjekte mit anonymen Klassen umsetzbar. Funktionsobjekte erfordern mehr Schreibarbeit als Closures, erfüllen aber den gleichen Zweck wie Closures.

Der Einfachheit halber und aufgrund der harten umzusetzenden Funktionalitäten für Closures wurden zunächst Funktionsobjekte den Closures in Java vorgezogen. Closures werden deshalb erst in naher Zukunft ein Teil der Java-Syntax sein. Entwurfsentscheidungen nach dem KISS Prinzip sind berechtigt, lösen aber unter Umständen nicht alle Probleme, sodass nur eine Problemverschiebung stattfindet. Zeiger auf Funktionen (C/C++) und Lambda-Ausdrücke (C#) sind in anderen Programmiersprachen gängig. Alle diese Sprachkonstrukte sind allerdings nur eine Implementierung des Strategiemusters, sofern man das Muster verstanden hat, versteht man auch den Anwendungszweck der Sprachkonstrukte.

Es ist eine gute Strategie Konzepte und Anwendungsfälle der gängigen Entwurfsmuster zu lernen. Entwurfsmuster und Java-Idiome lösen immer wiederkehrende Probleme auf eine standardisierte Art und Weise. Die Kernaussagen von Entwurfsmustern sind essentiell und basieren auf den Erkenntnissen der Praxis, die von sehr erfahrenen Programmierern extrahiert und zusammengefasst wurden. Das Strategiemuster ist ein Kandidat, der die Anforderungen für das Variieren von Algorithmen für andere Muster vorgibt und wesentliche Voraussetzungen für saubere Software definiert.

Die Kernaussagen des Strategiemusters sind:
  1. Trenne veränderlichen Quellcode von gleichbleibendem
  2. Kapsele Algorithmen damit diese dynamisch ersetzbar sind
  3. Programmiere auf Basis von Schnittstellen
  4. Ziehe Komposition (soweit möglich) der Vererbung vor
Das Strategiemuster fördert die Flexibilität und Evolvierbarkeit von Applikationen. Die innere Struktur einer Applikation ist dabei sehr flexibel an neue Funktionalitäten anpassbar, ohne Seiteneffekte in anderen Teilen einer Applikation hervorzurufen. Es ist ratsam das Strategiemuster an kritische Stellen einer Applikation, einzusetzen. Kritische Stellen sind aus fachlicher Sicht solche, von denen man weiß, dass sie in der Folge erweitert oder sogar ausgetauscht werden. 

Die Implementierung des Strategiemusters mit Closures, Funktionsobjekten oder Lambda-Ausdrücken spielt nicht die wesentliche Rolle. Wichtig ist die Erkenntnis, dass das Sprachkonstrukt nur ein Mittel für die Implementierung ist. Das Verständnis für Konzepte und Modelle hat neben dem Beherrschen von Sprachkonstrukten deshalb einen hohen Stellenwert für die  Realisierung sauberer Software.

In dem nachfolgenden Szenario wird eine Berechnung mit zwei Argumenten auf Basis einer möglichen Implementierung des Strategiemusters veranschaulicht. Die Implementierung hätte auch auf Basis einer Klassenhierarchie erfolgen können. Das Strategiemuster bietet sich im Kontext paralleler Klassenhierarchien an, um je nach konkreter Strategie die Instanzen von Klassen zu verknüpfen.

Strategie für eine einfache Berechnung:

public interface CalculationStrategy<T extends Number> {

    T operation(T firstArg, T secondArg);
}

Implementierung der Berechnung:

public class Calculator<T extends Number> {

    public T calculate(T firstArg, T secondArg, CalculationStrategy<T> calculation) {
      
        return calculation.operation(firstArg, secondArg);
    }
}

Unit-Test der Berechnung:

import static org.junit.Assert.*;
import org.junit.Test;

public class TestCalculator {

    @Test
    public void testAdd() {
      
        final Calculator<Integer> intCalculator = new Calculator<Integer>();
        int sum = intCalculator.calculate(1, 1, new CalculationStrategy<Integer>() {

            @Override
            public Integer operation(Integer firstArg, Integer secondArg) {
              
                return firstArg + secondArg;
            }                      
        });
      
        assertEquals(2, sum);
    }
  
    @Test
    public void testSubtract() {
      
        final Calculator<Integer> intCalculator = new Calculator<Integer>();
        int dif = intCalculator.calculate(1, 1, new CalculationStrategy<Integer>() {

            @Override
            public Integer operation(Integer firstArg, Integer secondArg) {
              
                return firstArg - secondArg;
            }                      
        });
      
        assertEquals(0, dif);
    }
}

Concept and model knowledge are important for the implementation of clean software!


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