(14.01.05 22:30)

Das DRY-Prinzip ist ein wichtiger und einleuchtender Grundsatz, denn wir beim Schreiben von Code wirklich beherzigen sollten. Schauen wir uns dazu ein Beispiel an:

Gegeben ist eine Modelklasse, welche so genannte Ressourcen verwaltet und auf der es möglich ist, ICreationListener zu registrieren, um das Erzeugen bestimmter Objekte zu observieren (Observermuster). Die ICreationListener-Schnittstelle ist wie folgt deklariert:

public interface ICreationListener {
  public void created(IntLiteral literal);
  public void created(StringLiteral literal);
  public void creatd(BooleanLiteral literal);
  // weitere Methoden weggelassen
  ...
}

Immer wenn im Modell z.B. ein neues Literal erzeugt werden muss, dann werden die registrierten CreationListener entsprechend benachrichtigt. Da die möglichen Klassen der zu erzeugenden Ressourcen abschliessend bekannt sind, und wir unnötige Typecasts vermeiden wollen, deklariert die ICreationListener-Schnittstelle entsprechend überladene created-Methoden.

Die Modell-Klasse implementiert für jede zu instantiierende Resource-Klasse entsprechende Factory-Objekte:

public class Model {

 ...

 private class IntLiteralFactory 
                  extends Resource {
    
   ...

   public Resource asNode(){
     IntLiteral literal = 
                 new IntLiteral(fModel);

     // weiterer Code weggelassen
     ...
     
     Iterator iter = 
               fCreationListeners.iterator();
     while (iter.hasNext()) {
       ICreationListener element = 
	(ICreationListener) iter.next();
       element.created(literal);
     } 

     return literal;
   }
 }

 private class StringLiteralFactory 
                     extends Resource {
    
   ...

   public Resource asNode(){
     StringLiteral literal = 
                    new StringLiteral(fModel);

     // weiterer Code weggelassen
     ...

     Iterator iter = 
                  fCreationListeners.iterator();
     while (iter.hasNext()) {
       ICreationListener element = 
        (ICreationListener)iter.next();
       element.created(literal);
     } 

     return literal;
   }
 }

Die Implementierungen von IntLiteral und StringLiteral sind praktisch identisch und unterscheiden sich bezüglich des instantiierten Typs und weiteren kleineren Details, die hier nicht von Interesse sind; die fett markierten Teile sind für beide Implementierungen identisch.

Die Variante, dieses gemeinsame Verhalten in die Methode unserer äusseren Klasse auszulagern, funktioniert nicht ohne weiteres. Da wir eine typensichere Listenerbenachrichtigung wollen, können wir nicht einfach über einen Literal-Basistyp arbeiten. Die folgende Variante scheidet daher aus:

  // Variante mit Literal Basisklasse
  //
  public void notifyListeners(Literal literal){

    Iterator iter = 
              fCreationListeners.iterator();
    while (iter.hasNext()) {
      ICreationListener element = 
       (ICreationListener)iter.next();
      // funktioniert nicht !!!
      element.created(literal);
    } 
  }

Besser ist die Variante, bei der die Benachrichtigung des jeweiligen Listeners an ein Notification-Objekt delegiert wird:

  // Variante mit INotification-Schnittstelle
  //
  
  private interface INotification {
    public void send(ICreationListener element);
  }

  private void notifyCreationListeners(
                        INotification notify) {
    Iterator iter = 
              fCreationListeners.iterator();
    while (iter.hasNext()) {
      ICreationListener element = 
       (ICreationListener)iter.next();
      notify.send(element);
    } 
  }  

Damit werden wir den redundanten Iterationscode los. Bleibt noch die Frage, wie wir die INotification-Schnittstelle in den jeweiligen Literal-Factories umsetzen. Die eleganteste Lösung besteht in der Verwendung einer anonymen Klasse. Das sieht dann illustriert an der Klasse IntLiteralFactory wie folgt aus:

  public Resource asNode(){
    final IntLiteral literal = 
           new IntLiteral(fModel);
    ...
      
    notifyCreationListeners(new INotification() 
    {
      public void send(
                   ICreationListener element) {
        element.created(literal);
      }
    });  
     
    return literal;
  }

So bleibt uns zum einen der typensichere Code erhalten, und wir haben es darüber hinaus geschafft, unnötige und gefährliche Codeduplikation zu vermeiden.

-nemo :-)

Über diesen Artikel diskutieren.

Zugriffe heute: 1 - gesamt: 3063.




Keep it DRY! - Nachtrag Hast und Eile töten Projekte

Druckbare Version