Wenn man eine Gruppe und einzelne Elemente hat – wo fügt man dann die Add-Methode hinzu? Also heißt es administrators.Add(user) oder user.Add(administrators)? Gibt es da eine Best-Practice? Quasi immer vom kleineren zum größeren, oder umgekehrt?
Auf die Beantwortung dieser Frage war ein Kopfgeld in Höhe von 50 Reputationspunkten ausgesetzt.
Das Kopfgeld wurde bereits vergeben.
3 Antworten
1
Aus meiner Sicht ist es abhängig von der Semantik.
Sachverhalte: 1. In einem Benutzersystem können Benutzer mehreren Gruppen hinzugefügt werden. Wenn man eine Gruppe löscht, löscht man damit aber nicht die Benutzer, weil dies zu einer Inkonsistenz in den anderen Gruppen führen würde. Ein Benutzer kann in einem Berechtigungssystem durchaus ohne Gruppenzugehörigkeit existieren. Auf der anderen Seite kann ein Benutzer aber nicht ohne Benutzersystem leben. Es gibt also einfache Referenzen und existenzielle Abhängigkeiten, die dringend zu unterscheiden sind. Was die existenzielle Abhängigkeit angeht, sollte ein Objekt immer nur von genaum einem Objekt existenziell abhängig sein. Es dürfen aber mehrere Referenzen auf ein Objekt existieren. 2. Die Navigation über Assoziationen ist in sehr vielen Fällen "natürlich" (z.B. Benutzersystem.getAlleBenutzer()), in anderen Fällen sicherlich "erwünscht" (benutzer.getGruppenmitgliedschaften()) aber nicht essentiell, da die "erwünschten" Assoziationen meistens Abkürzungen sind für die "natürlichen" Assoziationen (hier Iteration über Benutzersystem.getGruppen() und gruppe.istMitglied(benutzer)). 3. Existenzielle Abhängigkeiten sind immer natürliche Assoziationen. Natürliche Assoziationen müssen aber nicht existenziell sein.
Vorschlag: Es sollte immer von den natürlichen Assoziationen ausgegangen werden, weil diese die Klassen- und Objektstruktur zusammenhalten. Die gewünschten Assoziationen (Abkürzungen) sind nachzupflegen und synchron zur natürlichen Struktur zu halten.
Gibt es da eine Best-Practice? Quasi immer vom kleineren zum größeren, oder umgekehrt?
Dein Gefühl, dem größeren Objekt die Verantwortung zuzutragen, ist in den meisten Fällen "natürlich" und hier absolut richtig gewesen. Dennoch sollte man immer schauen, welche Bedeutung die Objekte im fachlichen Kontext haben und daraus die Verantwortlichkeiten ableiten. Das Beispiel mit dem Benutzersystem ist da noch sehr einfach.
2. Manche perfomantere technische Umsetzung insbesondere in Hibernate erzwingt manchmal das Management der Assoziation von der verkehrten Seite. (Siehe hierzu Hibernate - Das Praxisbuch für Entwickler, Sebastian Hennebrüder, Galileo Computing, S.217 ff.). Mein Leitspruch bleibt aber: Semantic first, d.h. Die Technik muss sich der Fachlichkeit anpassen, nicht umgekehrt, auch wenn es manchmal etwas langsamer läuft.
Das ganze geht auch problemlos umgekehrt wenn user ein Propertie hat "user.Gruppe = new List<IGruppen>();" Dann geht und ist durchaus logisch und sinnvoll. user.Gruppe.Add(administrators)
Klar geht das auch. Ich sage ja auch nicht, dass der User kein Property Groups haben darf. Üblicher ist meiner Meinung aber, dass eine Methode "Add" auf der übergeordneten Klasse angeboten wird. Andernfalls würde ich eine Methode AddTo erwarten. Es wird auch niemand gehindert in der Add-Methode der Klasse Gruppe in der User-Klasse das Groups-Property zu erweitern. Also etwa class Group { public Add(User user) { _users.Add(user); user.Groups.Add(this); } } Dann haben beide Objekte gegenseitig eine Referenz aufeinander.
"Üblicher ist meiner Meinung aber, dass eine Methode "Add" auf der übergeordneten Klasse angeboten wird." Und genau in diesem Punkt bin ich anderer Meinung. Erstens sollte "Add" immer noch von Collections (in welcher Form auch immer) angeboten werden. Dh. also administrators.users.Add(...) oder user.Gruppen.Add(...). Ich sehe keinen Sinn einer Klasse die keine Collection ist eine "Add"-Methode zu verpassen, es sei denn ich will wirklich rechnen. (z.b. "Point myPoint = new Point(1,1); myPoint.Add(new Point(5,5));")
Zudem ist es meiner Meinung nach ebenso wichtig den genauen Verwendungszweck zu berücksichtigen um an dieser Stelle keine Resourcen- und/oder Laufzeischleuder zu basteln. Es gibt für beide Varianten sinnvolle Anwendungszwecke.
Innerhalb der Klasse "Gruppe" user.groups.add(this) aufzurufen verstößt ersteinmal gegen das Geheimnisprinzip, weil Du deine Referenz veröffentlichst. Jeder kann an deinem User irgendeine Gruppe hinzufügen, weil Du die Collection herausreichst. Man kann sich dann mit Seiteneffekten herumärgern. Außerdem sollte der Benutzer die Hoheit über das Hinzufügen und entfernen seiner Mitgliedschaften haben, und nicht irgendwer, der irgendwann einmal die Referenz auf die Collection erhalten hat (Single Responsibility). Und wenn die Methode "add" nicht gefällt, einfach in "addGroup" umbenennen.
@Floyd: soweit ich sehe, wurde das Kommentar-Edit-Feature abgelehnt, und man kann nicht mehr dafür voten :-((( Mist, dass ich das nicht früher gesehen habe.
Das ist meißt von Fall zu Fall unterschiedlich und hängt in der Regel ganz davon ab was man erreichen möchte oder wie die Anforderungen aussehen.
Hier eine klare Aussage zu treffen ohne die Anforderungen und den Zweck zu kennen ist schwierig.
Hier stellt sich schon bei deinem einfachen Beispiel die Frage, was ist "user"? Wenn "user" eine einzelne Person ist müßte man dann nicht "user.Gruppe.Add(administrators)" schreiben anstatt "user.Add(administrators)"?!
Um auf dein Beispiel zurückzukommen und die verschiedenen Anwendungszwecke:
Wenn du in der Regel von den Gruppe auf die User schließen musst und du meißtens nur Gruppen und keine User als Ausgang hast ist die erste Variante schneller.
Wenn du hingegen eine Art Rechteverwaltung baust und somit meißt User hast und auf deren Gruppen schließen willst, ist die zweite Variante schneller.
Das Schließen von der Gruppe auf die enthaltenen Benutzer würde ich immer anbieten (natürliche Assoziation). Das Schließen von einem Benutzer auf seine Mitgliedschaften wäre eine Abkürzung, die synchron gehalten werden muss. Meine Meinung rührt daher, weil ich der Auffassung bin, dass Benutzer ohne Gruppen "leben" können, während Gruppen ohne Benutzer wenig sinnvoll sind. Deshalb wiegt die Assoziation von einer Gruppe zu den Benutzern stärker, als die Asoziation eines Benutzers zu seinen Mitgliedschaften.
Dann geht und ist durchaus logisch und sinnvoll.
user.Gruppe.Add(administrators)