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

[QUOTE=Landei]Mit anderen Worten: Du willst also das Anemic Domain Model Antipattern implementieren.
[/QUOTE]

Ich denke nicht, er möchte mehr das Repository Pattern implementieren.

Der folgt keiner sehr guten Argumentation zu diesem Thema. Schließlich ist ein Name ein Name und der Inhalt kann dennoch adäquat sein. Z.B. der EventBus, das Ding ist auch nur ein Manager von Events, heisst aber eben nicht Manager sondern EventBus, was die Architektur unterstreichen soll.
Und er liefert ja gleich ein Beispiel mit, EventHandler (vll. auch xxxListener) solle besser EventTarget heißen. In diesem Kontext ist das ziemlich egal da beides ausdrückt das Ziel eines Events zu sein.

Sag ichs mal so:
Tritt so eine Situation auf, sollte man die Objektorientiertem Teile des Projektes nochmal überdenken.

Grüße

Für mich hört sich das alles ein bisschen seltsam an. Was ich bis jetzt so gelesen habe, lässt darauf schließen, dass hier versucht wird ein DAO für alles zu basteln. Warum nicht für jede Domain ein eigenes DAO, sprich ein DAO für A und eins für B? Natürlich kann man alles in einen solchen “Manager” klatschen, aber was hat das für Vorteile? Keine, es verwischt nur Grenzen. Als Facade würde ich das noch verstehen, aber dann wären es immer noch unterschiedliche Domain-bezogene Implementierungen, die dahinter weggekapselt werden, sprich die jeweiligen DAOs. Und damit ist dann auch klar, dass A und B nur DTOs sind, also “dumme” Beans/POJOs, die passende DAOs benötigen

Im Grunde ist es doch so: Man hat irgendwelche Klassen, die man zur Repräsentation von Daten (z.B. Datensätze in einer Datenbank) und zur Verarbeitung dieser nutzen will, also DTOs. Damit man an solche Daten bzw. Instanzen solcher Klassen gelangt oder auch um Instanzen dieser zu verarbeiten, benötigt man irgend eine Schnittstelle, die z.B. mit der Datenbank kommunizieren kann, also DAOs. Und damit ist dann auch eigentlich klar, was wohin gehört. Ein DTO besitzt nur Attribute, die den Zustand seines zugehörigen Datensatzes in der Datenbank repräsentieren. Die Verarbeitung davon übernimmt das dazugehörige DAO.

Wenn man jetzt sagt, dass man ein bisschen stärker in Richtung Domain-Objects gehen will und den DTOs somit ein bisschen mehr Logik zutraut, dann kann man das ja machen, aber dann würde ich das nur auf das Nötigste beschränken, sprich Methoden wie save, delete, update und Getter für Objekte, die eine relationale Beziehung haben, z.B. getAuftraege. Filterungen wie getAuftraegeAbDatum und dergleichen aber nicht, das ist dann wieder Aufgabe des DAOs und es würde das DTO auch ziemlich unübersichtlich und chaotisch aussehen lassen. Jedenfalls sind diese Methoden nicht in der DTO-Klasse selbst ausimplementiert, sondern sind im Grunde nur Delegationen an die richtigen DAOs und dienen nur zur Vereinfachung, damit man sich für solche Aktionen, die ja alle paar Zeilen Code auftauchen können, nicht an das DAO wenden muss und der Code liest sich dadurch sicherlich auch ein Stück weit eleganter. Aber dennoch ist die Implementierung beider Parteien klar getrennt, selbst wenn man ein Domain-Driven-Model fahren will.

Natürlich hängt das alles auch wieder vom Fall ab, weil das, was ich hier beschrieben habe, könnte schon viele verwirren und aus dem Spiel nehmen, weil das nötige Wissen dazu fehlt und es ist schon eine ziemlich strikte Vorgehensweise, die für eine kleine Hobbyanwendung sicherlich zu viel des Guten ist, da hier die Anwendungsschichten klar getrennt werden, somit auch mit entsprechendem Aufwand verbunden und welcher Hobbyprogrammierer wird das schon machen. Im Businessbereich muss man sowas ja auch nur noch bedingt selbst implementieren dank SpringData und Co. Aber dennoch ist wohl entscheidend was der Kern der Aufmerksamkeit sein soll. Ich selbst würde das jedenfalls nicht in einer All-in-One-Klasse zusammenpacken, sondern klar getrennte Wege gehen und bestenfalls eine passende Fassade bereit stellen. Denn selbst wenn man sich sagt, Kunden und Aufträge haben eine Beziehung zu einander, dennoch sind das ja zwei verschiedene Zugriffsbereiche. Was ist, wenn ich nur eine Auftrags-Id habe und den Kunden gar nicht kenne? Wie soll ich dann den Auftrag laden, wenn ich dafür erst den Kunden brauche? Man könnte dafür ja auch wieder eine Methode im Manager implementieren, der den Auftrag anhand der ID laden kann, aber wie schon gesagt, das lässt die Grenzen verschwimmen und wenn dann noch Lieferanten, Hersteller, etc. hinzu kommen, dann wird das ganze nur noch chaotischer.

Okay, Buch ist so gut wie bestellt und den Artikel von Landei habe ich auch gelesen. Objektorientiert zu denken ist gar nicht so einfach. Was ich allerdings noch nicht verstanden habe: Wie bitte grenzt man jetzt das Repository Pattern (was eigentlich meine Intention ist) gegen das Anemic Domain Model Antipattern ab? So richtig sehe ich da keinen Unterschied!?

Beispiel für Eigenschaften des Kunden, die nicht öffentlich gemacht werden sollten:
Jeder Kunde hat ein Passwort, das ich als Hash in der Datenbank halte. Diesen möchte ich eigentlich nicht nach außen geben, weswegen ich gerne auf getHash/setHash verzichten würde. Beim Abspeichern muss ich allerdings den ganzen Datensatz zurück schreiben (inkl. Hash). Also muss ich den Hash durchaus mitladen und eben dann wieder abspeichern. Daher war meine Idee, über IKunde (Interface) die Methode „getHash()“ zu verbergen und sie dann in KundeImpl trotzdem zu haben, damit ich den Kunden als ganzes abspeichern kann.

Im Moment habe ich (so wie ich das sehe) zwei Möglichkeiten folgenden API-Bruch zu heilen

  public void save(IKunde k){/* IKunde -> KundeImpl = Bad Idea*/}```
a) Kunde wird ein reines DTO (und damit POJO) mit Zugriff auf alle Eigenschaften. Damit würde alle Business-Logik dem KundenManager übertagen.
b) Kunde wird ein komplexes Objekt, dass sich selbst verwalten kann, und über Methoden wie save() und getAufträge() verfügt. Wobei ich mir dabei allerdings die Frage stelle, ob es dann nicht ein bischen unübersichtlich wird!?

[QUOTE=Akeshihiro;22979]Für mich hört sich das alles ein bisschen seltsam an. Was ich bis jetzt so gelesen habe, lässt darauf schließen, dass hier versucht wird ein DAO für alles zu basteln. Warum nicht für jede Domain ein eigenes DAO, sprich ein DAO für A und eins für B?[/QUOTE]"Ein DAO für jede Domain" mache ich doch,  wenn ich sage, ich möchte einen AManager (=DAO für A-Instanzen) und einen BManager (=DAO für B-Instanzen) implementieren!?

In deinen Codebeispielen kann AManager B-Objekte holen, das meine ich damit. Dafür muss es einen BManager geben, der die methode getByA(A) oder besser findByA(A) hätte. Dein AManager macht im Moment Sachen für A UND B. Wenn du B-Objekte laden willst, dann greifst du nicht auf die Tabelle/View von A zu, sondern von B und damit muss dafür eigentlich ein eigenes DAO zur Verfügung gestellt werden, oder BManager, wie du es nennst.

[QUOTE=Akeshihiro]In deinen Codebeispielen kann AManager B-Objekte holen, das meine ich damit. Dafür muss es einen BManager geben, der die methode getByA(A) oder besser findByA(A) hätte. Dein AManager macht im Moment Sachen für A UND B. Wenn du B-Objekte laden willst, dann greifst du nicht auf die Tabelle/View von A zu, sondern von B und damit muss dafür eigentlich ein eigenes DAO zur Verfügung gestellt werden, oder BManager, wie du es nennst.[/QUOTE]:o Hab ich mich doch glatt “vertippt”. Ich hätte schwören können, ich hätte folgendes geschrieben (was ich auch eigentlich meinte):

  public B[] getByA(A a){...}```Danke für den Hinweis. (Werde es jetzt aber im Startpost nicht ändern, da das dann wohl nur zu nochmehr Verwirrung führen würde!?)

[QUOTE=Natac]:o Hab ich mich doch glatt “vertippt”. Ich hätte schwören können, ich hätte folgendes geschrieben (was ich auch eigentlich meinte):

  public B[] getByA(A a){...}```Danke für den Hinweis. (Werde es jetzt aber im Startpost nicht ändern, da das dann wohl nur zu nochmehr Verwirrung führen würde!?)[/QUOTE]

Dann hab ich nichts gesagt ... :D

Beispiel für Eigenschaften des Kunden, die nicht öffentlich gemacht werden sollten:
Jeder Kunde hat ein Passwort, das ich als Hash in der Datenbank halte. Diesen möchte ich eigentlich nicht nach außen geben, weswegen ich gerne auf getHash/setHash verzichten würde. Beim Abspeichern muss ich allerdings den ganzen Datensatz zurück schreiben (inkl. Hash). Also muss ich den Hash durchaus mitladen und eben dann wieder abspeichern. Daher war meine Idee, über IKunde (Interface) die Methode „getHash()“ zu verbergen und sie dann in KundeImpl trotzdem zu haben, damit ich den Kunden als ganzes abspeichern kann.

Sehe ich nicht so kritisch wie Du. Ich hätte (und habe) da einfach für alle Felder die Getter ins Interface gezogen. Aber Dein Punkt ist durchaus bedenkenswert. Als Idee dazu: Wie wäre es mit einer weiteren Methode in Denem Manager-Interface ungefähr so:

/**
  * Speichert für einen gegebenen Kunden ein neues Passwort.
  * Je nach implementierung wird dieses vor dem saven evtl gehasht.
  **/
public void savePwForKunde(IKunde kunte, String pw);

[QUOTE=nillehammer]Sehe ich nicht so kritisch wie Du. Ich hätte (und habe) da einfach für alle Felder die Getter ins Interface gezogen. Aber Dein Punkt ist durchaus bedenkenswert. Als Idee dazu: Wie wäre es mit einer weiteren Methode in Denem Manager-Interface ungefähr so:

/**
  * Speichert für einen gegebenen Kunden ein neues Passwort.
  * Je nach implementierung wird dieses vor dem saven evtl gehasht.
  **/
public void savePwForKunde(IKunde kunte, String pw);
```[/QUOTE]Das löst mein Problem ja nicht. die save()-methode speichert das Kunden-Objekt in die Datenbank. Komplett. Ist der Hash beim Kunden null, so wird auch null in die Datenbank gespeichert (bzw. wirft diese dann einen Fehler). Wenn ich also ein IKunde bekomme und keine getHash-Methode habe, dann muss ich den Hash für diesen Kunden ja irgendwo hernehmen (und alten kunden per ID laden um neuen zu speichern wollte ich eigentlich nicht)!? Oder würdest du dem IKunde noch eine Methode get/setHash verpassen!?
Hab gerade das Gefühl, ich stehe da etwas auf dem Schlauch. :confused:

EDIT: [QUOTE=Akeshihiro;23109]getByA(A) oder besser findByA(A) [/QUOTE]Wieso? Wo ist der Unterschied, ob ich es getByX oder findByX nenne? Das es kein normaler "Getter" ist, sollt doch in beiden Fällen klar sein!?

Die Diskussion habe ich jetzt nicht im Detail verfolgt. Aber sehe ich es richtig, dass das Problem THEORETISCH (!) und nur um den Gedanken zu klären (!) gelöst werden könnte durch sowas wie

interface Kunde {
    public void savePwTo(SomethingThatCanStorePws target);
    ...
}

Ich bin mir nicht sicher, ob das nicht auf „ganz normale Serialisierung“ rausläuft, aber ggf. lese ich den Thread nochmal genauer, vielleicht kommt dann die Erleuchtung, was denn das eigentliche Ziel ist.

Dieser „Your Coding Conventions Are Hurting You“-Artikel ist zwar schön (und einige andere Artikel von dem auch), aber … schon SEHR idealistisch. Zugegeben, vielleicht liegt meine Skepsis auch darin begründet, dass ich bei bestimmten Punkten (!) dazu tendiere, in das zu verfallen, was jetzt als „Anemic Domain Model Antipattern“ bezeichnet zu werden scheint. Aber… das hat einen Grund. Es ist vielleicht nicht schön und objektorientiert im besten Sinne, aber wer glaubt, eine nicht-triviale „schöne und objektorientierte“ Klassenstruktur entworfen zu haben, möge sich bei mir melden :wink:

Das löst mein Problem ja nicht. die save()-methode speichert das Kunden-Objekt in die Datenbank. Komplett. Ist der Hash beim Kunden null, so wird auch null in die Datenbank gespeichert (bzw. wirft diese dann einen Fehler).

Das ist dann ein Implementierungsfehler. Ein SQL-Update setzt jedenfalls nicht irgendwelche Felder NULL, wenn das nicht im Statement explizit so steht. Vielmehr werden die Felder, die nicht im UPDATE-Statement genannt sind, einfach so gelassen, wie sie sind.

Bei SQL-INSERT mag ein Fehlen des pwHash natürlich Probleme machen, wenn dafür ein NOT NULL Constraint existiert. Dem könnte man begegnen, indem man diesen Constraint NICHT setzt. Ich glaube nämlich, dass er garnicht nötig ist. Ein Kunde mit null-PW kann sich halt einfach nicht einloggen. Ist doch ne schöne Möglichkeit, einen Kunden (evtl. vorrübergehend ) zu deaktivieren.

Wie auch immer, ich will Dir jetzt nicht die eine oder andere Lösung als richtig verkaufen. Das einzige, was ich sage ist: Egal, was Du machst, definiere Dir ein sauberes Interface und programmiere dagegen.

Musst du ja nicht machen. Es hat sich halt einfach so eingebürgert (zumindest in der Java-Welt), dass DAO-Methoden mit find beginnen. Das hat auch irgendwo mit dem Sinn oder Ablauf zu tun. Getter liefern in der Regel nur das, was das Objekt in irgendeiner Form bereits hält, meist eben Attribute. Selbst wenn man Lazy-Loading mit ins Boot holt, so ist es dennoch gleich. Bei DAOs ist die Sache aber etwas anders. Diese halten ja keine Instanzen von Objekten, sondern müssen entsprechende Anfragen bei der Datenbank machen. Klarer wird es, wenn man den Begriff Filter noch mit hinzu nimmt. Das DAO hat ja nichts konkretes, das es liefern könnte, sondern man kann es eigentlich nur auf die Suche schicken und hoffen, dass da was dabei rauskommt. Daher find, „finde den Datensatz mit der ID x, mit dem Name y, mit der Schuhröße z“. Man könnte eben auch suchen sagen, weil man ja im Grunde sucht, aber da fällt mir was von Chuck Norris ein, daher finde ich find passender und da sich das so eingebürgert hat, wohl auch noch viele andere. „Chuck Norris geht nicht jagen, denn das beinhaltet die Möglichkeit des Versagens. Chuck Norris geht töten!“ Verstehste :smiley:

Wie du deine Methoden nennst, ist dir überlassen, und auch was den Rest betrifft. Das mit find war nur ein Vorschlag meinerseits. Wie gesagt, hat sich so eingebürgert, findest du eigentlich überall so und macht den Bezug auch um ein Vielfaches deutlichers als ein Pseudo-Getter, denn da hat man wieder die Charakteristik eines Getters.