Donnerstag, 10. Februar 2011

Loose Coupling

Die lose Kopplung von Softwarebausteinen ist ein Grundprinzip der Softwareentwicklung. Moderne Software-Architekturen, die serviceorientiert entworfen worden sind, nutzen lose miteinander gekoppelte Komponenten zur Flexibilisierung ihres Zusammenspiels. Die lose Kopplung ist bei Makrokomponenten und kleineren Entwurfseinheiten fest verankert. Entwurfsmuster nutzen das Konzept lose gekoppelter Softwarebausteine in vielfältiger Weise.

Ein Observer ist ein Muster, welches im Zusammenhang mit loser Kopplung häufig zuerst genannt wird. Die klassische Eigenschaft des Observer-Musters ist die Übermittlung von Zustandsänderung eines Objektes an beliebig viele andere Objekte. Diese Nachrichtenform nennt man Broadcasting. Broadcastings haben einen eingeschränkten Anwendungsfall in dem eins-zu-n Beziehungen relevant sind.

Eine Facade entkoppelt Client- von Serverkomponenten, ein Mediator zentralisiert die Verknüpfungen von Objekten und führt dadurch zu lose gekoppelten Objekten und das Command Muster verpackt Requests in Ereignisobjekte, um dem Prinzip der losen Kopplung gerecht zu werden. Die Musterbeispiele sind vielfältig, bei denen die lose Kopplung eine Rolle spielt.

Eng verbunden mit der losen Kopplung sind die Kohäsion (Single Responsibility Principle) und das Trennen der Verantwortlichkeiten (Separation of Concerns) bei Softwarebausteinen. Grundsätzlich wird durch die lose Kopplung und hohe Kohäsion die Evolvierbarkeit einer Software-Anwendung verbessert. Die hohe Kohäsion von Softwarebausteinen und deren lose gekoppelten Assoziationen schaffen isolierte Einheiten. Die einfachere Testbarkeit der isolierten Einheiten und weniger Seiteneffekte bei den in Assoziation stehenden Objekten sind weitere Merkmale loser gekoppelter Softwarebausteine.

Ein Entwurfsmuster, das die Kopplung zwischen Softwarebausteinen reduziert und eine Menge von Klassen als Einheit auftreten lässt, ist das Chain of Responsibility Pattern. Ereignisse, die in einer Klasse produziert werden, können bei diesem Muster an andere Klassen gesendet werden. Das Muster bietet deshalb sehr gute Voraussetzungen für die Weiterentwicklung ohne konkrete Logik in den Methoden einer Klasse anpassen zu müssen.

Die nachfolgende Implementierung von FizzBuzz nutzt das Muster, um auf Basis einer übergebenen Zahl die FizzBuzz konforme Ausgabe zu produzieren.

FizzBuzz Schnittstelle

interface FizzBuzz {

    public String answer(int number);
}

FizzBuzz Implementierung

public class FizzBuzzQuiz implements FizzBuzz {

    private final FizzBuzzRule answerRuleChain = createAnswerRuleChain();

    private FizzBuzzRule createAnswerRuleChain() {
       
        final FizzBuzzRule fizzBuzzAnswerRule = new FizzBuzzAnswerRule();
        final FizzBuzzRule buzzAnswerRule = new BuzzAnswerRule();
        final FizzBuzzRule fizzAnswerRule = new FizzAnswerRule();
        final FizzBuzzRule numberAnswerRule = new NumberAnswerRule();   
       
        fizzBuzzAnswerRule.setNext(buzzAnswerRule);
        buzzAnswerRule.setNext(fizzAnswerRule);
        fizzAnswerRule.setNext(numberAnswerRule);
       
        return fizzBuzzAnswerRule;
    }
   
    @Override
    public String answer(int number) {
   
        if(number < 0 ||
           number > Integer.MAX_VALUE) {
           
            throw new IllegalArgumentException("FizzBuzz number out of range: " + number);
        }
               
        return answerRuleChain.executeRule(number);   
    }
}

FizzBuzz Basisklasse der Regelimplementierungen

abstract class FizzBuzzRule {

    private FizzBuzzRule next;
   
    FizzBuzzRule setNext(final FizzBuzzRule next) {
       
        this.next = next;
       
        return getNext();
    }
   
    private FizzBuzzRule getNext() {
       
        return next;
    }

    String executeRule(int partNumber) {
       
        if(isNextRuleAvailable()) {
           
            if(executeRulePart(partNumber)) {
               
                return ruleResult(partNumber);
            }
           
            return getNext().executeRule(partNumber);
        }
       
        return ruleResult(partNumber);
    }
       
    private boolean isNextRuleAvailable() {
       
        return null != getNext();
    }

    boolean executeRulePart(int partNumber) {
       
      return partNumber >= replacementNumber() ?
                                       partNumber % replacementNumber() == 0 : false;
    }

    String ruleResult(int partNumber) {
       
        return toString();
    }
   
    abstract int replacementNumber();
}

FizzBuzz Regelimplementierungen

class FizzAnswerRule extends FizzBuzzRule {

    @Override
    int replacementNumber() {
       
        return 3;
    }
   
    @Override
    public String toString() {
       
        return "fizz";
    }
}

class BuzzAnswerRule extends FizzBuzzRule {

    @Override
    int replacementNumber() {
      
        return 5;
    }
  
    @Override
    public String toString() {
      
        return "buzz";
    }
}

class FizzBuzzAnswerRule extends FizzBuzzRule {
   
    @Override
    int replacementNumber() {
       
        return 15;
    }
       
    @Override
    public String toString() {
       
        return "fizzbuzz";
    }
}

class NumberAnswerRule extends FizzBuzzRule {
   
    @Override
    int replacementNumber() {
       
        return 0;
    }
   
    @Override
    String ruleResult(int partNumber) {
       
        return String.valueOf(partNumber);
    }
}

FizzBuzz Testklasse

import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.Test;

public class FizzBuzzQuizTest {

    private FizzBuzz fizzBuzzQuiz;

    @Before
    public void setUp() {
       
        fizzBuzzQuiz = new FizzBuzzQuiz();
    }
   
    @Test(expected=IllegalArgumentException.class)
    public void fizzBuzzMinValueTest() {
                   
        fizzBuzzQuiz.answer(-1);
    }
   
    @Test(expected=IllegalArgumentException.class)
    public void fizzBuzzMaxValueTest() {
                   
        fizzBuzzQuiz.answer(Integer.MAX_VALUE + 1);
    }
   
    @Test
    public void fizzBuzzValueTest() {
   
        assertEquals("0", fizzBuzzQuiz.answer(0));
        assertEquals("1", fizzBuzzQuiz.answer(1));
        assertEquals("2", fizzBuzzQuiz.answer(2));
        assertEquals("fizz", fizzBuzzQuiz.answer(3));
        assertEquals("4", fizzBuzzQuiz.answer(4));
        assertEquals("buzz", fizzBuzzQuiz.answer(5));       
        assertEquals("fizz", fizzBuzzQuiz.answer(6));
        assertEquals("7", fizzBuzzQuiz.answer(7));
        assertEquals("8", fizzBuzzQuiz.answer(8));
        assertEquals("fizz", fizzBuzzQuiz.answer(9));
        assertEquals("buzz", fizzBuzzQuiz.answer(10));
        assertEquals("fizzbuzz", fizzBuzzQuiz.answer(15));
        assertEquals("16", fizzBuzzQuiz.answer(16));
        assertEquals("17", fizzBuzzQuiz.answer(17));
        assertEquals("fizz", fizzBuzzQuiz.answer(18));
        assertEquals("19", fizzBuzzQuiz.answer(19));
        assertEquals("buzz", fizzBuzzQuiz.answer(20));
        assertEquals("fizzbuzz", fizzBuzzQuiz.answer(30));
        assertEquals("31", fizzBuzzQuiz.answer(31));   
    }
}


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