2008-09-22 5 views
18

Wenn ein Getter eine Eigenschaft zurückgibt, z. B. eine List anderer verwandter Objekte zurückgibt, sollten diese Liste und ihre Objekte unveränderlich sein, um Code außerhalb der Klasse zu verhindern, indem der Status dieser Objekte geändert wird, ohne dass das Hauptobjekt dies weiß?Betrachtet man die Kapselung von Objekten, sollten Getter eine unveränderliche Eigenschaft zurückgeben?

Zum Beispiel, wenn ein Contact Objekt, ein getDetails Getter hat, die eine List von ContactDetails Objekte zurückgibt, dann beliebigen Code aufrufen, dass Getter:

  1. ContactDetail Objekte aus dieser Liste entfernen kann, ohne die Contact Objekt zu wissen, von es.
  2. kann jedes ContactDetail Objekt ohne das Contact Objekt ändern, das davon kennt.

Also was sollen wir hier tun? Sollen wir einfach dem aufrufenden Code vertrauen und leicht änderbare Objekte zurückgeben oder auf die harte Tour gehen und eine unveränderliche Klasse für jede veränderbare Klasse erstellen?

Antwort

7

Es ist eine Frage, ob Sie in Ihrem Code "defensiv" sein sollten. Wenn Sie der (einzige) Nutzer Ihrer Klasse sind und sich selbst vertrauen, dann besteht kein Grund für Unveränderbarkeit. Wenn dieser Code dennoch funktionieren muss, oder Sie Ihrem Benutzer nicht vertrauen, machen Sie alles, was externalisiert wird, unveränderlich.

Das heißt, die meisten Eigenschaften, die ich erstelle, sind veränderbar. Ein gelegentlicher Benutzer verpfuscht dies, aber wiederum ist es seine/ihre Schuld, da eindeutig dokumentiert ist, dass Mutation nicht über veränderbare Objekte auftreten sollte, die über Getter empfangen werden.

+3

Das ist eine interessante Unterscheidung zu machen - Wetter oder nicht Sie besitzen den Code. Ich denke NIEMALS an etwas außerhalb der Klasse, die ich schreibe, als sicher. Ich bin nicht davon überzeugt, dass es einen Vorteil beim Kodieren mit der Annahme gibt, dass jemand, der dich anruft, es immer richtig machen wird ... Kann es tatsächlich schneller sein, ein paar Zeilen zu überspringen, wenn es einmal in deiner Karriere 20 oder 30 Minuten kostet Debuggen? Die Eingabe dieser zusätzlichen Zeilen ist praktisch kostenlos im Vergleich zu der Zeit, die Sie für ein Projekt ausgeben. –

5

Wenn Sie die Kontrolle über den aufrufenden Code haben, dann ist es am wichtigsten, dass die von Ihnen getroffene Wahl an den richtigen Stellen gut dokumentiert ist.

1

Ich habe eine schreibgeschützte Version der Liste oder zumindest eine Kopie zurückgegeben. Jedes in der Liste enthaltene Objekt muss jedoch bearbeitbar sein, es sei denn, sie sind vom Design her unveränderbar.

2

Hängt vom Kontext ab, wirklich. Aber im Allgemeinen, ja, sollte man als defensiven Code wie möglich schreiben (Array-Kopien zurück, readonly Wrappers um Sammlung s etc. zurück). In jedem Fall sollte es klar dokumentiert sein.

1

Ich denke, Sie werden feststellen, dass es sehr selten ist, dass jeder getable unwandelbar ist.

Sie können Ereignisse auslösen, wenn eine Eigenschaft in solchen Objekten geändert wird. Auch keine perfekte Lösung.

Dokumentation ist wahrscheinlich die pragmatische Lösung;)

6

Es hängt vom Kontext ab. Wenn die Liste änderbar sein soll, hat es keinen Sinn, die API der Hauptklasse mit Methoden zu überladen, um sie zu mutieren, wenn List über eine vollkommen gute API verfügt.

Wenn die Hauptklasse jedoch mit Mutationen nicht umgehen kann, müssen Sie eine unveränderliche Liste zurückgeben - und die Einträge in der Liste müssen möglicherweise auch selbst unveränderlich sein.

Vergessen Sie jedoch nicht, dass Sie eine benutzerdefinierte Listenimplementierung zurückgeben können, die weiß, wie Sie sicher auf Mutationsanforderungen reagieren können, indem Sie Ereignisse auslösen oder erforderliche Aktionen direkt ausführen. In der Tat ist dies ein klassisches Beispiel für eine gute Zeit, eine innere Klasse zu verwenden.

0

Als ich anfing war ich immer noch stark unter dem Einfluss von HIDE YOUR DATA OO Principals LOL. Ich würde mich hinsetzen und darüber nachdenken, was passieren würde, wenn jemand den Zustand eines der Objekte ändern würde, die von einem Grundstück freigelegt wurden. Soll ich sie nur für externe Anrufer lesen lassen? Sollte ich sie überhaupt nicht bloßstellen?

Sammlungen brachten diese Ängste auf die Spitze. Ich meine, jemand könnte alle Objekte in der Sammlung entfernen, während ich nicht hinsehe!

Ich erkannte schließlich, dass, wenn Ihre Objekte so enge Abhängigkeiten von ihren äußerlich sichtbaren Eigenschaften und ihren Typen halten, wenn jemand sie an einem schlechten Platz berührt, Sie Boom gehen, ist Ihre Architektur fehlerhaft.

Es gibt gute Gründe, Ihre externen Eigenschaften schreibgeschützt und ihre Typen unveränderbar zu machen. Aber das ist der Eckfall, nicht der typische, imho.

+0

Das Verstecken von Daten verhindert nicht nur, dass andere Objekte Ihre Variablen ändern - es geht darum sicherzustellen, dass Benutzer Ihrer Klasse nicht von einer Änderung betroffen sind interne Darstellung. Wenn Sie Ihr internes Objekt freigeben, wird auch verhindert, dass Sie Aktionen hinzufügen, die ausgeführt werden, wenn Mitglieder geändert werden. – DJClayworth

1

Ihr erster Imperativ sollte sein, dem Demeter-Gesetz zu folgen oder 'Tell do not ask'; der Objektinstanz mitteilen, was zu tun ist, z.B.

Objektorientierter Code weist Objekte an, Dinge zu tun. Verfahrenscode erhält Informationen und handelt dann mit diesen Informationen. Wenn man ein Objekt bittet, die Details seiner Interna zu enthüllen, bricht die Kapselung, es ist prozeduraler Code, keine Sound-OO-Programmierung und wie Will bereits gesagt hat, ist es ein fehlerhaftes Design.

Wenn Sie dem Demeter-Ansatz folgen, tritt jede Änderung des Zustands eines Objekts durch seine definierte Schnittstelle auf, daher sind Nebenwirkungen bekannt und werden kontrolliert. Dein Problem verschwindet.

3

Im besonderen Fall einer Sammlung, List, Set, oder Karte in Java, ist es einfach, einen unveränderlichen Blick auf die Klasse zurückzukehren return Collections.unmodifiableList(list);

Natürlich verwenden, wenn es möglich ist, dass die Träger-Daten wird noch geändert, dann müssen Sie eine vollständige Kopie der Liste erstellen.

0

Zunächst sind Setter und Getter ein Anzeichen für ein schlechtes OO. Im Allgemeinen die Idee von OO ist, dass Sie das Objekt bitten, etwas für Sie zu tun. Setzen und Erhalten ist das Gegenteil. Sun hätte einen anderen Weg finden sollen, Java-Beans zu implementieren, damit die Leute dieses Muster nicht aufgreifen und es für "richtig" halten.

Zweitens sollte jedes Objekt, das Sie haben, eine Welt für sich sein - im Allgemeinen, wenn Sie Setter und Getter verwenden, sollten sie relativ sichere unabhängige Objekte zurückgeben. Diese Objekte können oder können nicht unveränderbar sein, weil sie nur erstklassige Objekte sind. Die andere Möglichkeit ist, dass sie native Typen zurückgeben, die immer unveränderlich sind. Wenn man also sagt "Setter und Getter sollten etwas Unveränderliches zurückgeben", macht das keinen Sinn.

Um immutable Objekte selbst zu machen, sollten Sie die Mitglieder virtuell immer in Ihrem Objekt final machen, es sei denn, Sie haben einen starken Grund nicht (Endgültig sollte der Standard sein, "veränderbar" sollte ein Schlüsselwort sein, das diesen Standard außer Kraft setzt) . Dies bedeutet, dass Objekte wo immer möglich unveränderlich sind.

Wie für vordefinierte quasi-Objekt Dinge, die Sie weitergeben könnten, empfehle ich Sie Sachen wie Sammlungen und Gruppen von Werten, die zusammen in ihre eigenen Klassen mit ihren eigenen Methoden zusammengehen.Ich übergebe praktisch niemals eine ungeschützte Sammlung, nur weil du keine Anleitung gibst, wie sie verwendet wird, wo die Verwendung eines gut gestalteten Objekts offensichtlich sein sollte. Sicherheit ist auch ein Faktor, da der Zugriff auf eine Sammlung in der Klasse praktisch unmöglich ist, so dass es praktisch unmöglich ist sicherzustellen, dass die Klasse immer gültig ist.

+0

"Setter und Getter sind ein Anzeichen für schlechte OO" - das ist wirklich, ehrlich gesagt, völlig unwahr. – Calanus

+0

Obwohl es oft diskutiert wird, ist es nicht eindeutig unwahr, und wenn Sie beginnen, OO wirklich zu verstehen, erkennen Sie, dass es tatsächlich ein guter Indikator für ein Problem ist (Überbeanspruchung, das ist). In OO fordert man ein Objekt auf, etwas mit seinen eigenen Daten zu tun. Daher muss man extern auf Daten zugreifen, um anzuzeigen, dass der Code, der auf die Daten zugreift, es falsch macht, stattdessen Daten weitergibt und dieses Objekt bittet, mit seinen eigenen Daten zu arbeiten es ist eine eigene Methode. Dies ist nicht 100%, aber es ist ein gültiges Ziel. –

+0

Es ist auch komisch, wie die Leute emotional auf dieses Konzept reagieren - da es so ist, wie sie "OO" gelernt haben, können sie sich nicht vorstellen, dass es falsch ist. Ich schließe mich nicht aus, ich musste einige Tage darüber nachdenken, bevor ich sicher war, dass das Entwerfen ohne Eigenschaften besser war - aber ich finde es immer noch amüsant, wie die Antwort immer "Das ist nicht richtig" ohne Erklärung oder Unterstützung ist --Referenz vielleicht. Alles andere als "Ich weiß es, weil ich es fühle". –

3

Joshua Bloch sagt in seinem ausgezeichneten "Effektives Java" -Buch, dass man immer wieder defensive Kopien machen sollte, wenn man so etwas zurückgibt. Das kann ein wenig extrem sein, besonders wenn die ContactDetails Objekte nicht Clonable sind, aber es ist immer der sichere Weg. Im Zweifelsfall immer die Code-Sicherheit gegenüber der Leistung bevorzugen - es sei denn, das Profiling hat gezeigt, dass das Klonen ein echter Performance-Engpass ist.

Es gibt verschiedene Schutzstufen, die Sie hinzufügen können. Sie können einfach das Mitglied zurückgeben, das im Wesentlichen jeder anderen Klasse Zugriff auf die Interna Ihrer Klasse gewährt. Sehr unsicher, aber in Fairness weit verbreitet. Es wird später auch Probleme verursachen, wenn Sie die Interna so ändern möchten, dass die ContactDetails in einem Set gespeichert werden. Sie können eine neu erstellte Liste mit Verweisen auf dieselben Objekte in der internen Liste zurückgeben. Dies ist sicherer - eine andere Klasse kann die Liste nicht entfernen oder hinzufügen, aber sie kann die vorhandenen Objekte ändern. Zurückgeben einer neu erstellten Liste mit Kopien der ContactDetails-Objekte. Das ist der sichere Weg, kann aber teuer sein.

Ich würde dies besser machen. Geben Sie keine Liste zurück, sondern geben Sie einen Iterator über eine Liste zurück. Auf diese Weise müssen Sie keine neue Liste erstellen (List verfügt über eine Methode zum Abrufen eines Iterators), aber die externe Klasse kann die Liste nicht ändern. Sie können die Elemente weiterhin ändern, es sei denn, Sie schreiben einen eigenen Iterator, der die Elemente nach Bedarf klont. Wenn Sie später zur internen Verwendung einer anderen Sammlung wechseln, kann es immer noch einen Iterator zurückgeben, so dass keine externen Änderungen erforderlich sind.

+2

und wenn der Iterator die Methode remove implementiert, kann er auch die ursprüngliche Liste ändern ... – Nicolas

Verwandte Themen