Vererbung Vererbungs-Hierarchie von Wrappern

Landei

Top Contributor
Ich habe hin und wieder das Problem, dass ich sowas habe:

Java:
class A {}
class B extends A{}
class C extends B{}


class AWrapper {
    private final A a;
    public AWrapper(A a) { 
        this.a = a;  
    }
}

class BWrapper extends AWrapper {
    private final B b;
    public BWrapper(B b) { 
        super(b);
        this.b = b;  
    }
}

class CWrapper extends BWrapper {
    private final C c;
    public CWrapper(C c) { 
        super(c);
        this.c = c;  
    }
}

Das funktioniert, aber natürlich ist es dumm, mehrere Referenzen auf das gleiche Objekt zu halten. Es wäre auch möglich, a in AWrapper protected zu machen, und zu casten, aber das ist wirklich hässlich. Eine Lösungsmöglichkeit, die ich gefunden habe, um das zu vermeiden, benutzt abstrakte Klassen:

Java:
abstract class AWrapper {
   public abstact A getA();
}

abstract class BWrapper extends AWrapper {
   public abstact B getB();
   public A getA() { return getB(); }
}

abstract class CWrapper extends BWrapper {
   public abstact C getC();
   public B getB() { return getC(); }
}

class Wrappers {
   public static AWrapper newAWrapper(final A a) {
       return new AWrapper(){  public A getA() {return a;} };
   }

   public static BWrapper new BWrapper(final B b) {
       return new BWrapper(){  public B getB() {return b;} };
   }

   public static CWrapper new CWrapper(final C c) {
       return new CWrapper(){  public C getC() {return c;} };
   }
}

Trotzdem nicht hübsch, und wenn ich von den einzelnen Wrappern noch in "andere Richtungen" ableiten will, komme ich um eine zusätzliche Reihe von konkreten Klassen (statt des Tricks mit den statischen Methoden) nicht herum.

Man kann sicher auch Generics benutzen, aber nicht immer will man einen Typparameter haben.

Wie löst ihr sowas?
 
Zuletzt bearbeitet:

FArt

Top Contributor
Das verallgemeinerte Problem habe ich verstanden.

Ich hatte bisher aber noch nie dieses Problem. Meine Vermutung ist, dass es eine suboptimale Lösung für ein Problem ist, welches man auch anders lösen kann.

Wozu sind die Wrapper gut? Kann man das nicht besser über Interfaces lösen? Ist diese Verbungshierarchie so sinnvoll, oder kann man die Vererbung evtl. auch über Interfaces und Delegation ersetzen (zumindest teilweise)? Kann ich beim Einsatz von Interfaces die Wrapper durch dynamische Proxies ersetzen? Kann ich hier beliebte Pattern verwenden, wie z.B. Dekorierer. Adapter, Fassade?

Hast du ein konkretes Beispiel, in dem man den sinnvollen Einsatz der Vererbung im Zusammenhang mit den Wrappern nachvollziehen kann?
 

Landei

Top Contributor
Der aktuelle Fall tritt bei Monaden-Transformern in meinem highJ-Projekt auf. Ein Monaden-Transformer "umhüllt" sozusagen eine(n) vorhanden Funktor, Applicative, Monade u.s.w., und stülpt die zusätzlichen Features "seines" Typs (Identity, Maybe, Either, List, Cont) der "umhüllten" Typklasse über. Die gruseligen Details finden sich hier: Haskell/Monad transformers - Wikibooks, open books for an open world

Mir ist klar, dass für so etwas eine parallele Hierarchie notwendig ist, aber natürlich will ich den Overhead so klein wie möglich halten.

Ich erinnere mich aber vage, dass ich solche Probleme auch schon im "richtigen Leben" hatte, und auch schon nicht elegant lösen konnte.

Die Patterns klingen ja alle interessant, aber meine Erfahrung ist, dass sie mit Vererbung "quer" zu ihrer eigenen Definition nicht gut klarkommen.
 
M

Marcinek

Gast
Hallo,

wenn A, B , C auch voneinader erben, kann man das Problem mit Generics lösen.

Damit brauchst du in dem jeweiligen Wrapper nix casten.

Gruß,

Martin
 

FArt

Top Contributor
Ein x "umhüllt" sozusagen ein y, und stülpt die zusätzlichen Features "seines" Typs (a, b, c) der "umhüllten" Typklasse über.
Spricht meiner Ansicht nach für Delegation anstatt für Vererbung.

Die Patterns klingen ja alle interessant, aber meine Erfahrung ist, dass sie mit Vererbung "quer" zu ihrer eigenen Definition nicht gut klarkommen.

Vererbung "quer" ?

Ich persönlich verwende Vererbung lediglich für echte Basisfunktionalität z.B. in Form von abstrakten Klassen. In der Regel lässt sich alles weiter mit Delegation wesentlich schöner und flexibler lösen.

Wenn z.B. Dekoriere für dich sinnvoll wäre (um das zu sagen habe ich dein konkretes Beispiel nicht gut genug verstanden), dann würde mein Design ungefähr so aussehen: Das Decorator Design Pattern (Dekorierer Entwurfsmuster))
 

Marco13

Top Contributor
Ich hatte bisher aber noch nie dieses Problem. Meine Vermutung ist, dass es eine suboptimale Lösung für ein Problem ist, welches man auch anders lösen kann.

Dann hast du dir noch keine anderen Fragen von Landei (oder auch die highj-Bibliothek) angesehen :D Er löst viele Probleme, von denen man gar nicht wußte, dass sie existieren :D (Nicht abwertend oder böse gemeint, eher im Gegenteil: Irgendjemand MUSS die Grenzen ausloten)

Zur eigentlichen Frage... die hab' ich wohl (mal wieder) nicht richtig verstanden. Vielleicht auch nur den Punkt, was genau die Aufgabe der Wrapper ist, bzw. welche Ziele durch die Vererbungen (auf Seite der Inneren Klassen und auf Seite der Wrapper-Klasse) verfolgt werden. Spontane Gedanken kreisten darum, dass...

- das nach einem typischen Fall aussieht, wo man einen Generic-Parameter hinzufügt (aber es stimmt, dass man damit "sparsam" umgehen sollte - es kann (speziell bei geschachtelten Generics) leicht unübersichtlich werden)

- Interfaces und Kovarianz dem ganzen ja entgegen kommen sollten...
Java:
class A {}

class B extends A {}

class C extends B {}

interface AWrapper
{
    public A getContents();
}

interface BWrapper extends AWrapper
{
    @Override
    public B getContents();
}

interface CWrapper extends BWrapper
{
    @Override
    public C getContents();
}

class Wrappers 
{
    public static AWrapper newAWrapper(final A a) 
    {
        return new AWrapper(){  public A getContents() {return a;} };
    }
  
    public static BWrapper newBWrapper(final B b) 
    {
        return new BWrapper(){  public B getContents() {return b;} };
    }
  
    public static CWrapper newCWrapper(final C c) 
    {
        return new CWrapper(){  public C getContents() {return c;} };
    }
}

- So ein cast des Rückgabetyps einer protecteten Methode IMHO nicht schlimm ist. Für solche Sachen ist protected ja quasi da...
Java:
class A {}

class B extends A {}

class C extends B {}

class AWrapper 
{
    private A a;
    public AWrapper(A a) { this.a = a; }
    public A getA() { return a; }
 }
  
class BWrapper extends AWrapper 
{
    public BWrapper(B b) { super(b); } // Wegen dieses Konstruktors
    public B getB() { return (B)getA(); } // ist das hier OK
}

class CWrapper extends BWrapper 
{
    public CWrapper(C c) { super(c); } // Wegen dieses Konstruktors
    public C getC() { return (C)getB(); } // ist das hier OK
}

Aber sicher sollte man sich das im Kontext nochmal genauer überlegen...
 
S

Spacerat

Gast
Bevor es irgendwie hässlich tief geschachtelt wird, breche ich die Vererbungshierarchie auf eine (bzw. zwei inkl. Mutterklasse) Ebene runter und verwende ggf. ein Interface.
Aber irgendwie versteh' ich nicht, wofür bei B und C Wapper benötigt werden. C ist instanceof B und B ist instanceof A demzufolge ist auch C instanceof A. Was genau muss denn da wann gewrapped werden und vor allem wofür?
Wenn man sich mal anschaut, wie es bei den vorhandenen Primitiv-Wrappern funktioniert, so erben sie alle (bis auf Character) von einer abstrakten Mutterklasse (Number) und da hats dann im Prinzip die Methoden "getA()", "getB()" und "getC()". Jetzt musst du nur noch einen Weg finden, wie du aus einem A ein B oder ein C machst.
 

Landei

Top Contributor
Java:
class A {}

class B extends A {}

class C extends B {}

interface AWrapper
{
    public A getContents();
}

interface BWrapper extends AWrapper
{
    @Override
    public B getContents();
}

interface CWrapper extends BWrapper
{
    @Override
    public C getContents();
}

class Wrappers 
{
    public static AWrapper newAWrapper(final A a) 
    {
        return new AWrapper(){  public A getContents() {return a;} };
    }
  
    public static BWrapper newBWrapper(final B b) 
    {
        return new BWrapper(){  public B getContents() {return b;} };
    }
  
    public static CWrapper newCWrapper(final C c) 
    {
        return new CWrapper(){  public C getContents() {return c;} };
    }
}

Stimmt, das sind einfach kovariante Rückgabetypen. Meine Version mit getA bis getC war Käse, so gefällt mir das schon besser.

Casten wie in der zweiten Version will ich wirklich nicht.

Nebenbei ist mir ein viel besseres Beispiel eingefallen, wo man so eine Hierarchie braucht: Gruppen

Eine Halbgruppe hat einfach eine transitive binäre Operation, nennen wir sie mal "dot":

Java:
interface Semigroup<A> {
   public A dot(A x, A y);
}

Hat man noch zusätzlich ein neutrales Element, hat man ein Monoid:

Java:
interface Monoid<A> extends Semigroup<A> {
   public A identity();
}

Beispiele wären Addition, Multiplikation und (String- und Listen-) Verkettung.

Gibt es zu jedem Element auch ein inverses Element, ist man bei einer Gruppe angelangt:

Java:
interface Group<A> extends Monoid<A> {
   public A inverse(A x);
}

Wenn man nun eine Klasse [c]Pair<A,B>[/c] hat, ist diese natürlich eine Halbgruppe, wenn man Halbgruppen für A und B kennt: Man wendet einfach die Operation der Halbgruppe für A auf das jeweils erste, und die Operation der Halbgruppe für B auf das jeweils zweite Element an, und packt es wieder in ein Paar:

Java:
class PairSemigroup<A,B> implements Semigroup<Pair<A,B>> {
   private Semigroup<A> semiA;
   private Semigroup<B> semiB;

   public PairSemigroup(Semigroup<A> semiA, Semigroup<B> semiB) {
       this.semiA = semiA;
       this.semiB = semiB;
   }

   public Pair<A,B> dot(Pair<A,B> x, Pair<A,B> y) {
       return new Pair<A,B>(semiA.dot(x._1(),y._1()), semiB.dot(x._2(), y._2()));
   }
}

Will man das Ganze jetzt auf PairMonoid und PairGroup ausdehnt, läuft man in das beschriebene Problem - wobei hier Marco's Lösung gut anwendbar wäre.
 
Zuletzt bearbeitet:

FArt

Top Contributor
[OT]
Dann hast du dir noch keine anderen Fragen von Landei (oder auch die highj-Bibliothek) angesehen :D Er löst viele Probleme, von denen man gar nicht wußte, dass sie existieren

Nein. Ich followe keinem User und sehe Fragen immer nur, wenn eine gerade aktuell ist und ich mal wieder zufällig online bin und nachsehe.
Landei fällt mir in der Regel mit Antworten auf.

Aber vielleicht lohnt es sich ja mal nachzusehen ...
[/OT]
 

Ähnliche Java Themen

Neue Themen


Oben