Diamanten in Java


Java verfügt weder über Mehrfach- noch über Mixin-Vererbung. Insofern erscheint eine Diskussion des Diamant-Problems reichlich müßig. Trotzdem bin ich gerade darüber gestolpert, und habe einen Ansatz entwickelt, der – allerdings auf Kosten der Übersichtlichkeit – Code-Duplizierung vermeidet. Nehmen wir an, folgende Interfaces seien gegeben:

public interface Base {
    public void base();
    public void useBase();
}

public interface Foo extends Base {
    public void foo();
    public void useFoo();
}

public interface Bar extends Foo {
    public void bar();
    public void useBar();
}

public interface FooBar extends Foo, Bar {
    public void useFooAndBar();
}

Die useXYZ-Methoden können allgemein unter Rückgriff auf die anderen Methoden des Interfaces oder der Super-Interfaces implementiert werden, und sollten deshalb in abstrakte Klassen ausgelagert werden. Natürlich kann die abstrakte Klasse für FooBar nur von einer der beiden in Frage kommenden abstrakten Klassen erben, sagen wir von der zu Foo. Dann haben wir folgendes Szenario:

public abstract class BaseAbstract implements Base {
    public abstract void base();
    public void useBase() {
        System.out.println("using Base.base()");
        base();
    }
}

public abstract class FooAbstract extends BaseAbstract implements Foo {
    public abstract void foo();
    public void useFoo() {
        System.out.println("using Foo.foo() and Base.base()");
        foo();
        base();
    }
}

public abstract class BarAbstract extends BaseAbstract implements Bar {
    public abstract void bar();
    public void useBar() {
        System.out.println("using Bar.bar() and Base.base()");
        bar();
        base();
    }
}

public abstract class FooBarAbstract extends FooAbstract implements FooBar {
    public abstract void bar();
    //DUPLICATED
    public void useBar() {
        System.out.println("using Bar.bar() and Base.base()");
        bar();
        base();
    }
    public void useFooAndBar() {
        System.out.println("using everything");
        base();
        foo();
        bar();
    }
}

Wie man sieht, bleibt bei dieser Version nichts anderes übrig, als in FooBarAbstract den Inhalt von Bar zu wiederholen.

Mein Vorschlag, das Problem zu lösen, verletzt so einige OO-Prinzipien und wird dadurch unflexibel gegenüber nachträglichen Änderungen. Ich würde ihn nur empfehlen, wenn die Hierarchie wirklich feststeht und man sich damit erhebliche Code-Duplizierungen erspart (also wenn Anzahl und Umfang der useFoo()- und useBar()-Methoden recht groß ist. Der Trick beruht darauf, die Implementierung von useBar() schon in BaseAbstract zu erledigen. Dazu wird eine Hilfsmethode _bar() benötigt, die dort wo vorhanden (also in Bar und FooBar) einfach zum „echten“ bar(), das ja vom Nutzer der abstrakten Klasse bereitgestellt werden muss, weiterleitet:

public abstract class BaseAbstract implements Base {
    public abstract void base();

    public void useBase() {
        System.out.println("using Base.base()");
        base();
    }
    
    //Ersatz-Methode für bar()
    protected void _bar() {
       throw new UnsupportedOperationException(); 
    }
    
    //Code, der eigentlich in BarAbstract gehört
    public void useBar() {
        System.out.println("using Bar.bar() and Base.base()");
        _bar(); //benutzt die Ersatz-Methode
        base();
    }
}

public abstract class FooAbstract extends BaseAbstract implements Foo {
    public abstract void foo();
    public void useFoo() {
        System.out.println("using Foo.foo() and Base.base()");
        foo();
        base();
    }
}

public abstract class BarAbstract extends BaseAbstract implements Bar {
    public abstract void bar();

    //Delegation, sonst funktioniert useBar() nicht
    @Override protected void _bar() { bar(); }
}

public abstract class FooBarAbstract extends FooAbstract implements FooBar {
    public abstract void bar();

    //Delegation, sonst funktioniert useBar() nicht
    @Override protected void _bar() { bar(); }
    
    public void useFooAndBar() {
        System.out.println("using everything");
        base();
        foo();
        bar();
    }
}

Wie so oft wird mein Code keinen Schönheitspreis gewinnen, weil er mit den Beschränkungen von Java kämpft. Es ist ein Kompromiss, der zur Wahrung des DRY-Prinzips andere Konventionen verletzt. Man sollte sich sehr genau überlegen, ob diese Technik an einer bestimmten Stelle wirklich sinnvoll ist, aber wenn sie es ist, kann sie einige Arbeit sparen.

Wie immer bin ich natürlich neugierig, ob jemand eine bessere Idee hat.

Advertisements

Ein Gedanke zu “Diamanten in Java

  1. Du kannst eine zentrale abstrakte „mega“ klasse die foo foobar und bar implementiert die du dann weiter spezialisieren kannst definieren.

    Oder du lagerst den code der methoden in eigene instanzen aus und delegierst damit alle methoden.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s