Best Practice: obj.get() vs get(obj)

Hallo,

Sorry vorweg für den ungenauen Thread-Titel. Mir ist kein besserer eingefallen.

Ich stehe gerade vor einer Design-Entscheidung, und kann mich nicht richtig für einen Punkt durchringen. Darum frage ich jetzt mal euch:

Ich habe (zur Vereinfachung) zwei Klassen:
A - Daten + Container von Bs
B - Element von A
(Natürlich hört es nicht wirklich bei B auf. Aber ich erspare uns mal den ganzen Rattenschwanz und belasse es bei A und B ;))

Dabei kann A unter anderem viele B-Datensätze enthalten, die ich nicht umbedingt immer im Speicher haben möchte, sondern nur nachladen würde, wenn sie gebraucht werden. Die Frage ist jetzt, wie ich den Bezug zwischen A und B baue:

Idee 1: obj.getBs()```public class A{
private AManager m;

protected A(AManager m){this.m = m;}

public B[] getBs(){return m.getBs(this);}
}

public class AManager {
public A create(){return new A();}

protected B[] getBs(A a){ /* get bs from database */}
}```
Vorteile:

  • Ich kann mir die B-Objekte bequem über das A-Objekt holen und bilde damit die Zuordnungen korrekt ab.
  • Ich könnte A später hinter einem Interface verstecken.
    Nachteil:
  • Ich muss Instanzen von A umständlich über die Factory-Methode AManager.create() erzeugen.

Idee 2: getBs(A obj)```public class A{
public A(){super();}
}

public class AManager {
public B[] getBs(A a){ /* get bs from database */}
}```
Vorteil: Leichter Umgang mit A, kann direkt mit Konstruktor erzeugt werden.
Nachteil. Will ich B-Instanzen haben, muss ich diese über den AManager holen.

Vielleicht noch gut zu wissen: A und B sollen später Bestandteil einer API sein.

Welchen Ansatz würdet ihr wählen? Was ist besser? Welche Dinge habe ich nicht bedacht?

Ich habe den Code und den Anwendungsfall nicht nachvollzogen. Aber das Bekanntmachen von Kindklassen in einer Elternklasse ist ein absolutes No-Go! Das Managen und laden von Instanzen gehört ausgelagert.

Echt?
Wenn ich einen Kunden (A) habe, sollte der Kunde keine Methode getAufträge() (getBs) besitzten!? (vielleicht ist die Formulierung “Kind” und “Eltern” auch schlecht gewählt. !?)

Das Vermute ich auch: Das angedeutete “No-go” würde ich verstehen, wenn zwischen A und B eine Vererbungs-Beziehung bestehen würde, aber das ist hier nicht der Fall.

Vielleicht sind A und B etwas ZU abstrakt, um was dazu sagen zu können - geht es einen Hauch konkreter (auch in Bezug auf die gewünschte Verwendung, irgendeine Beschreibung des Zusammenhangs…?!)

Genau das meine ich. Mal an einem konkreten Beispiel: Die Oberklasse Tier sollte niemals wissen, dass es Tiger, Elefanten und Ameisen gibt…

Ähh: War das nicht so gemeint, dass B von A erbt?!? Falls nicht, bitte meinen Einlass ignorieren, falls doch, bitte beachten.

Ähh2: Ok, habe Eltern und Kind falsch als Vererbungsbeziehung interpretiert…

Also erstmal: Es ist keine Vererbungs-Beziehung!

Mal gucken, ob ich es konketer hinbekomme:

Ich habe verschiedene Dateninstanzen, die miteinander in Beziehung stehen (sagen wir Kunden und Aufträge). Über eine API soll jetzt der Zugriff auf diese Daten ermöglicht werden. Prinzipiell steht dafür für jede Klasse eine Manager-Instanz bereit, der sich um die Instanzen einer solchen Klasse kümmert und Methoden wie loadAll(), delete(X instance), etc… besitzt (bswp. KundenManager, AuftragsManager).

Nun haben die Klassen natürlich Beziehungen untereinander: Ein Kunde hat n Aufträge. Die Frage ist jetzt, wie ich diese Aufträge abfragen kann. Kann ich die Aufträge direkt beim Kunden erfragen (Idee 1), oder muss ich mit dem Kunden zum AuftragsManager gehen und mir alle Aufträge für diesen Kunden holen (Idee 2)? Was wäre aus sicht der API-Nutung schöner?

Ich hoffe, dass das Problem jetzt ein wenig deutlicher geworden ist!?

EDIT: Habs jetzt mal in „Container“ und „Elemente“ geändert. Ich hoffe das ist eindeutiger. :smiley:

In der rein fachlichen Sicht möchte man, dass ein Kunde seine Aufträge kennt. Hier wäre also eine Collection von Aufträgen direkt im Kunden wünschenswert. Man will sicherlich auch, dass ein Auftrag seinen Kunden kennt. Hier also eine Kundenvariable. Nun hat man eine bidirektionale Beziehung. Das würde wohl rauskommen, wenn man ein UML-Fachklassendiagramm eins zu eins in Code übersetzt.

Da die korrekte Verwendung von Collections und das Gültig-Halten beider Enden der bidirektionalen Beziehung einiges an zusätzlichem Programmier-Aufwand in den Entity-Klassen bedeutet, vermeide ich es persönlich, wenn es irgend geht. Ich würde eindeutig für einen Service plädieren, der eine Methode

allAuftraegeByKunde(Kunde kunde)

bereit stellt.

P.S. Sorry für meine anfängliche Verwirrung.

EDIT: Habs jetzt mal in “Container” und “Elemente” geändert. Ich hoffe das ist eindeutiger.

Perfekt! So nenne ich das in meinem Code auch immer. Habe sogar generische Interfaces geschrieben, die genau so heißen. Da muss ich nicht umdenken, wie praktisch :twisted:

Das ist schwierig pauchal zu beantworten, weil es auch von der Fachdomäne abhängt. Es geht da, etwas schwammig-freitagnachmittagig formuliert, auch darum, wie stark der Zusammenhang zwischen den Klassen ist, wie „elementar“ die Kinder für die Existenz der Eltern sind, oder in welchen Zusammenhängen die Verbindung (am Beispiel: die Verbindung Kunde->Aufträge) bekannt sein soll.

Ich hatte diese Frage schonmal ganz allgemein und theoretisch gestellt, und eigentlich würde ich jetzt den Thread ausgraben, in dem ich schonmal darüber philosophiert habe, aber … :frowning:

Wenn ich es richtig verstanden habe, steht dieser „Manager“ ja nicht in Frage. Es soll also IMMER eine Manager-Klasse geben, die

public B[] getBs(A a) 

anbietet? In den Beispielen war jetzt nur der Unterschied, ob diese Methode von A.getBs() aus aufgerufen wird oder nicht.

Die Vor/Nachteile verstehe ich nicht ganz, speziell:

  • A hinter einem Interface verstecken zu können sollte man auf jeden Fall in betracht ziehen
  • (Daraus folgend) : Instanzen über eine „create()“-Methode erstellen zu müssen ist normal und OK.

Mal abschicken, bestimmt habe zwischendrin schon andere geantwortet :wink:

[QUOTE=Marco13]Wenn ich es richtig verstanden habe, steht dieser “Manager” ja nicht in Frage. Es soll also IMMER eine Manager-Klasse geben, die

public B[] getBs(A a) 

anbietet?[/QUOTE]Der Manager an sich ist nicht fest, war für mich aber ein guter Ausgangspunkt, um möglichst flexibel auf die Daten zugreifen zu können. Denn wenn ich etwas komplexere Abfragen anbieten möchte (gib mir die Aufträge der Kunden a, b und c die im Zeitraum xy liegen), dann würde ich ungern die Kunden-Klasse damit zumüllen.

[QUOTE=Marco13;21509] In den Beispielen war jetzt nur der Unterschied, ob diese Methode von A.getBs() aus aufgerufen wird oder nicht. [/QUOTE]Jepp.

[QUOTE=Marco13;21509]Die Vor/Nachteile verstehe ich nicht ganz, speziell:

  • A hinter einem Interface verstecken zu können sollte man auf jeden Fall in betracht ziehen
  • (Daraus folgend) : Instanzen über eine “create()”-Methode erstellen zu müssen ist normal und OK.[/QUOTE]Okay, dann fällt der “Negativ”-Punkt, dass ich nicht new A() schreiben kann gedanklich weg.

Hmja, genau das ist eben so ein Fall, den ich mit den Verbindungen und Zusammenhängen meinte. Wenn man schon eine Funktion hat wie

List<Auftrag> aufträge = kunde.getAufträge();

// Wobei
class Kunde {
    List<Auftrag> getAufträge() { return manager.get(this); }
}

dann würde es IMHO etwas inkonsistent wirken, wenn man „ähnliche“ Funktionen an anderer Stelle anbieten würde. Es würde sich auch die Frage stellen, ob der „Manager“ dann noch „public“ wäre, oder wirklich nur im Hintergrund (package-private) arbeitet. Wieder nur als Denkanstoß (ich hab’ das Gefühl, gerade schrecklich, schrecklich wenig konkret zu sein…) : Das angedeutete „Filtern“ nach bestimmten Kriterien könnte ja ggf. komplett unabhängig von dieser Frage sein

List<Auftrag> gefiltert = AuftragsFilterer.filtereNachDatum(kunde.getAufträge(), datum);
// vs.
List<Auftrag> gefiltert = AuftragsFilterer.filtereNachDatum(manager.get(kunde), datum);

macht an sich nicht viel Unterschied, aber natürlich kennt niemand den „echten“ Fall, und weiß nicht, ob es nicht sinnvoller wäre, diese Funktionalität DOCH im Manager selbst oder im Kunden anzubieten.

So. Jetzt schalt’ ich die Sparflamme, auf der mein Gehirn im Moment läuft, aber endgültig ab :slight_smile:

[QUOTE=Marco13]Es würde sich auch die Frage stellen, ob der „Manager“ dann noch „public“ wäre, oder wirklich nur im Hintergrund (package-private) arbeitet.[/QUOTE]Naja, einige Manager sind sicherlich public, da die Obersten Container-Elemente ja irgendwo herkommen müssen. Ob ich alle public mache, habe ich auch schon überlegt.

[QUOTE=Marco13;21523]Wieder nur als Denkanstoß (ich hab’ das Gefühl, gerade schrecklich, schrecklich wenig konkret zu sein…) [/QUOTE]Deine Denkanstöße helfen mir auf jeden Fall weiter. :slight_smile: Und viel konkreter gehts gerade auch nicht, da ich mich ja gerade noch entwerfe.

Das angedeutete „Filtern“ nach bestimmten Kriterien könnte ja ggf. komplett unabhängig von dieser Frage sein

List<Auftrag> gefiltert = AuftragsFilterer.filtereNachDatum(kunde.getAufträge(), datum);
// vs.
List<Auftrag> gefiltert = AuftragsFilterer.filtereNachDatum(manager.get(kunde), datum);

macht an sich nicht viel Unterschied, aber natürlich kennt niemand den „echten“ Fall, und weiß nicht, ob es nicht sinnvoller wäre, diese Funktionalität DOCH im Manager selbst oder im Kunden anzubieten.
Hm… Dann doch lieber anders herum: Filter-Instanz erzeugen und der Abfrage übergeben. Dann könnte ich die Datenbank-Abfrage noch optimieren. Aber das war ja nur ein Beispiel und wird jetzt wirklich OT. :wink:

Jup. Die Datenbankabfrage IST ein Filter, und es könnte vorteilhaft sein, die Struktur analog dazu zu machen (also überzogen suggestiv sowas wie
List select(List from, Matcher where)
:wink: ) aber darum ging es ja nicht im Kern.

Ich denke ich werde alles Beziehungstechnische über die Manager abbilden. nillehammer hat mich da überzeugt, das einheitlich und einfach zu gestalten.

Zu der Factory-Methode KundeManager.create() habe ich nochmal eine Frage:
Wenn ich nun mit KundeManager.create() ein Interface (IKunde) zurückgebe, dann würde die Methode KundeManager.save(IKunde k) ja auch dieses Interface bekommen (quasi “von außen”).

Angenommen KundeImpl hätte protected methoden, die ich gerne im Manager nutzen würde, müsste ich dann jeden IKunde mit k instanceof KundeImpl prüfen, oder gibt es da einen besseren Weg Typtechnisch vom Interface (IKunde) wieder an die Klasse (KundeImpl) zu kommen, die ich mal ürsprünglich über die create()-Methode (hinter dem Interface) rausgegeben habe!?

OT: Wie mach ich in diesem Forum Inline-Code!?

Die Frage lässt sich leicht beantworten, wenn man sich anschaut, ob die “Container-Klasse” (also Kunden in deinem Beispiel) für die “Element-Klasse” (also Aufträge) “verantwortlich” ist, oder anders ausgedrückt, ob eine Instanz der Elementklasse ohne zugehörige Instanz der Containerklasse existieren könnte. Bei Aufträgen ist das nicht der Fall, also spricht nichts dagegen, dass sie auch vom jeweiligen Kunden “verwaltet” werden.

Ich hoff ich laber jetz nix falsches xD
Wenn du nur Nachladen willst, dann bau dir ein Datenbankinterface, dass die Daten aus der DB nachlädt / abfragt?
ALso mit festen Methoden wie z.b.

public ArrayList<Auftrag> getAuftraegeFromKunde(Kunde k)

Grüße

@Landei : Und selbst wenn ich kompliziertere Abfragen auf die Aufträge habe, würdest du die entsprechenden Methoden beim Kunden einbauen?
Würdest du einen neuen Auftrag auch über den Kunden anlegen? Oder löschen? Wie verhalte ich mich dann, wenn ich einem Kunden einen Auftrag zum löschen übergebe, der ihm nicht gehört? Führe ich das trotzdem aus (id des Auftrags reicht ja zum löschen)? Nachdem ich mir all diese Fragen gestellt habe, bin ich zum den Schluss gekommen, dass es keine Container im eigentlichen Sinne geben wird. Der Kunde wird ein reines Datenobjekt, und es wird die Methode AuftragManager.getAuftraege(Kunde k) geben, die alle Aufträge zu diesem Kunden zurück gibt. Damit habe ich eine Instanz (AuftragManager), die für alle Aufträge verantwortlich ist, und ich muss mich nicht mit den Fragen von oben rumschlagen. :smiley:

Meine aktuelle Frage ist aber noch wie vor:
Wenn ich nun mit KundeManager.create() ein Interface (IKunde) zurückgebe, dann würde die Methode KundeManager.save(IKunde k) ja auch dieses Interface bekommen (quasi „von außen“).
Angenommen **KundeImpl **hätte Protected-Methoden, die ich gerne im Manager nutzen würde, müsste ich dann jeden **IKunde **mit k instanceof KundeImpl prüfen, oder gibt es da einen besseren Weg Typtechnisch vom Interface (IKunde) wieder an die Klasse (KundeImpl) zu kommen, die ich mal ürsprünglich über die create()-Methode (hinter dem Interface) rausgegeben habe!?

Hmm du könntest Kunde von IKunde erben lassen und einen konstruktor in Ikunde einbaueni auf den Kunde zugreifen kann. Dann einfach ne Art Kopie zurückgeben.
Solltest du dich ernsthaft mit java beschäftigen, empfehle ich dir das Buch “Der Weg zum Java Profi”

Grüße
//EDIT: Mach doch für Die Arten der Kunde ne gemeinsame Basisklasse / Interface

Danke für die Buchempfehlung, werde ich mal gucken.

[QUOTE=java4ever]Hmm du könntest Kunde von IKunde erben lassen und einen konstruktor in Ikunde einbaueni auf den Kunde zugreifen kann. Dann einfach ne Art Kopie zurückgeben.
Solltest du dich ernsthaft mit java beschäftigen, empfehle ich dir das Buch “Der Weg zum Java Profi”

Grüße
//EDIT: Mach doch für Die Arten der Kunde ne gemeinsame Basisklasse / Interface[/QUOTE] Gegeben ist:```
class KundeImpl implements IKunde{}

public class KundeManager{
public IKunde create(){return new KundeImpl();}

public void save(IKunde kunde){
// Wie komme ich hier von IKunde nach KundeImpl!?
}
}```Wenn ich deinen Vorschlag richtig verstanden habe, schlägst du mir vor einen Konstruktor in KundeImpl anzulegen, der ein IKunde-Objekt als Parameter bekommt und sich die Wert kopiert. Das wäre eine Idee. Aber was ist mit internen Werten, die ich auch nicht übers Interface rausgebe? Auf die hätte ich dann ja auch keinen Zugriff…

Das ist -wie Du schon selbst gemerkt hast- ein Bruch in der API. Auf der einen Seite nen schön definiertes Interface IKunde, auf der andern aber dann doch die Notwendigkeit, auf nicht sichtbare Eigenschaften/Methoden zuzugreifen. Passt irgendwie nicht zusammen. Denke nochmal drüber nach und ziehe ggf. weitere Methoden ins Interface. Das wäre so allgemein der Tipp. Um welche internen Eigenschaften/Methoden geht es denn? Kannst du da konkreter werden?

Ansonsten:

public void save(IKunde kunde){
   // Wie komme ich hier von IKunde nach KundeImpl!? 

   // Anwort: Garnicht! Der Manager muss mit dem Interface klar kommen.
   // Was für einen Sinn hätte dieses sonst?
}```

[QUOTE=Natac]@Landei : Und selbst wenn ich kompliziertere Abfragen auf die Aufträge habe, würdest du die entsprechenden Methoden beim Kunden einbauen?
Würdest du einen neuen Auftrag auch über den Kunden anlegen? Oder löschen? Wie verhalte ich mich dann, wenn ich einem Kunden einen Auftrag zum löschen übergebe, der ihm nicht gehört? Führe ich das trotzdem aus (id des Auftrags reicht ja zum löschen)? Nachdem ich mir all diese Fragen gestellt habe, bin ich zum den Schluss gekommen, dass es keine Container im eigentlichen Sinne geben wird. Der Kunde wird ein reines Datenobjekt, und es wird die Methode AuftragManager.getAuftraege(Kunde k) geben, die alle Aufträge zu diesem Kunden zurück gibt. Damit habe ich eine Instanz (AuftragManager), die für alle Aufträge verantwortlich ist, und ich muss mich nicht mit den Fragen von oben rumschlagen. :D[/QUOTE]

Mit anderen Worten: Du willst also das Anemic Domain Model Antipattern implementieren.

Schon die Verwendung der Worts „Manager“ (und „Data“, „Controller“, „Handler“, „Helper“…) in einem Domänenmodell sollte stutzig machen: http://www.carlopescio.com/2011/04/your-coding-conventions-are-hurting-you.html

Kurz gesagt:

Aber selbstverständlich hat jeder das Recht auf seine eigenen Fehler…