it-swarm.com.de

Ist es ein Anti-Pattern, wenn eine Klasseneigenschaft eine neue Instanz einer Klasse erstellt und zurückgibt?

Ich habe eine Klasse namens Heading, die einige Dinge tut, aber auch das Gegenteil des aktuellen Überschriftenwerts zurückgeben sollte, der schließlich durch Erstellen einer neuen Instanz von Heading Klasse selbst.

Ich kann eine einfache Eigenschaft namens reciprocal haben, um die entgegengesetzte Überschrift des aktuellen Werts zurückzugeben und dann manuell eine neue Instanz der Heading-Klasse zu erstellen, oder ich kann eine Methode wie createReciprocalHeading() automatisch erstellen Erstellen Sie eine neue Instanz der Heading-Klasse und geben Sie sie an den Benutzer zurück.

Einer meiner Kollegen empfahl mir jedoch, einfach eine Klasse Eigenschaft mit dem Namen reciprocal zu erstellen, die über ihre Getter-Methode eine neue Instanz der Klasse selbst zurückgibt.

Meine Frage ist: Ist es nicht ein Anti-Muster für eine Eigenschaft einer Klasse, sich so zu verhalten?

Ich finde das besonders weniger intuitiv, weil:

  1. In meinen Augen sollte eine Eigenschaft einer Klasse keine neue Instanz einer Klasse zurückgeben, und
  2. Der Name der Eigenschaft, reciprocal, hilft dem Entwickler nicht, sein Verhalten vollständig zu verstehen, ohne Hilfe von IDE) zu erhalten oder die Getter-Signatur zu überprüfen.

Bin ich zu streng darüber, was eine Klasseneigenschaft tun soll, oder ist es ein berechtigtes Anliegen? Ich habe immer versucht, den Status der Klasse über ihre Felder und Eigenschaften und ihr Verhalten über ihre Methoden zu verwalten, und ich kann nicht erkennen, wie dies zur Definition der Klasseneigenschaft passt.

24
53777A

Es ist nicht unbekannt, Dinge wie Copy () oder Clone () zu haben, aber ja, ich denke, Sie haben Recht, sich darüber Sorgen zu machen.

nehmen wir zum Beispiel:

h2 = h1.reciprocal
h2.Name = "hello world"
h1.reciprocal.Name = ?

Es wäre schön, wenn Sie gewarnt würden, dass die Eigenschaft jedes Mal ein neues Objekt ist.

sie könnten auch annehmen, dass:

h2.reciprocal == h1

Jedoch. Wenn Ihre Überschriftenklasse ein unveränderlicher Werttyp wäre, könnten Sie diese Beziehungen implementieren, und wechselseitig könnte ein guter Name für die Operation sein

22
Ewan

Eine Schnittstelle einer Klasse führt den Benutzer der Klasse dazu, Annahmen darüber zu treffen, wie sie funktioniert.

Wenn viele dieser Annahmen richtig und wenige falsch sind, ist die Schnittstelle gut.

Wenn viele dieser Annahmen falsch und wenige richtig sind, ist die Schnittstelle Müll.

Eine verbreitete Annahme über Eigenschaften ist, dass das Aufrufen der Funktion get billig ist. Eine weitere häufige Annahme zu Eigenschaften ist, dass das zweimalige Aufrufen der Funktion get die gleiche Funktion zurückgibt.


Sie können dies umgehen, indem Sie Konsistenz verwenden, um die Erwartungen zu ändern. Beispielsweise können Sie für eine kleine 3D-Bibliothek, in der Sie Vector, RayMatrix usw. benötigen, diese so gestalten, dass Getter wie Vector.normal Und Matrix.inverse Sind so ziemlich immer teuer. Selbst wenn Ihre Schnittstellen durchweg teure Eigenschaften verwenden, sind die Schnittstellen bestenfalls so intuitiv wie eine, die Vector.create_normal() und Matrix.create_inverse() verwendet - aber ich kenne kein starkes Argument dafür Durch die Verwendung von Eigenschaften wird eine intuitivere Benutzeroberfläche erstellt, auch wenn sich die Erwartungen geändert haben.

17
Peter

Eigenschaften sollten sehr schnell zurückkehren, bei wiederholten Aufrufen denselben Wert zurückgeben, und das Abrufen ihres Werts sollte keine Nebenwirkungen haben. Was Sie hier beschreiben, sollte nicht als Eigenschaft implementiert werden.

Ihre Intuition ist im Großen und Ganzen richtig. Eine Factory-Methode gibt bei wiederholten Aufrufen nicht denselben Wert zurück. Es wird jedes Mal eine neue Instanz zurückgegeben. Dies disqualifiziert es so ziemlich als Eigentum. Es ist auch nicht schnell, wenn die spätere Entwicklung der Instanzerstellung Gewicht verleiht, z. Netzwerkabhängigkeiten.

Eigenschaften sollten im Allgemeinen sehr einfache Operationen sein, die einen Teil des aktuellen Status der Klasse abrufen/festlegen. Fabrikähnliche Vorgänge erfüllen diese Kriterien nicht.

10
Brad Thomas

Ich glaube nicht, dass es eine sprachunabhängige Antwort darauf gibt, da das, was eine „Eigenschaft“ ausmacht, eine sprachspezifische Frage ist und was ein Anrufer einer „Eigenschaft“ erwartet, auch eine sprachspezifische Frage ist. Ich denke, der fruchtbarste Weg, darüber nachzudenken, besteht darin, darüber nachzudenken, wie es aus Sicht des Anrufers aussieht.

In C # zeichnen sich Eigenschaften dadurch aus, dass sie (konventionell) groß geschrieben werden (wie Methoden), aber keine Klammern haben (wie öffentliche Instanzvariablen). Was erwarten Sie, wenn Sie den folgenden Code ohne Dokumentation sehen?

var reciprocalHeading = myHeading.Reciprocal;

Als relativer C # -Anfänger, aber einer, der Microsoft Property Usage Guidelines gelesen hat, würde ich unter anderem erwarten, dass Reciprocal:

  1. ein logisches Datenelement der Klasse Heading sein
  2. sie können kostengünstig anrufen, sodass ich den Wert nicht zwischenspeichern muss
  3. fehlen beobachtbare Nebenwirkungen
  4. das gleiche Ergebnis erzielen, wenn es zweimal hintereinander aufgerufen wird
  5. (vielleicht) bieten Sie ein ReciprocalChanged Ereignis an

Von diesen Annahmen sind (3) und (4) wahrscheinlich richtig (vorausgesetzt, Heading ist ein unveränderlicher Werttyp, wie in Ewans Antwort ), (1) ist umstritten, (2) ist unbekannt, aber auch umstritten, und (5) macht wahrscheinlich keinen semantischen Sinn (obwohl was auch immer hat eine Überschrift sollte vielleicht ein HeadingChanged Ereignis haben). Dies legt für mich nahe, dass in einer C # -API "get oder berechne den Kehrwert" nicht als Eigenschaft implementiert werden sollte, aber insbesondere wenn die Berechnung billig und Heading unveränderlich ist, Es ist ein Grenzfall.

(Beachten Sie jedoch, dass keines dieser Probleme damit zu tun hat, ob der Aufruf der Eigenschaft eine neue Instanz erstellt, nicht einmal (2). Das Erstellen von Objekten in der CLR an und für sich ist ziemlich billig.)

In Java sind Eigenschaften eine Methodennamenskonvention. Wenn ich sehe

Heading reciprocalHeading = myHeading.getReciprocal();

meine Erwartungen ähneln denen oben (wenn auch weniger explizit dargelegt): Ich erwarte, dass der Anruf billig, idempotent und ohne Nebenwirkungen ist. Außerhalb des JavaBeans-Frameworks ist das Konzept einer „Eigenschaft“ in Java jedoch nicht allzu aussagekräftig, und insbesondere wenn eine unveränderliche Eigenschaft ohne entsprechende setReciprocal() betrachtet wird, gilt jetzt die Konvention getXXX() etwas altmodisch. Von Effektives Java , zweite Ausgabe (bereits mehr als acht Jahre alt):

Methoden, die eine Nicht -boolean -Funktion oder ein Attribut des Objekts zurückgeben, für das sie aufgerufen werden, werden normalerweise mit einem Substantiv, einer Nominalphrase oder einer Verbalphrase benannt, die mit dem Verb get… beginnt. Es gibt ein Vokalkontingent, das behauptet, dass nur die dritte Form (beginnend mit get) akzeptabel ist, aber es gibt wenig Grundlage für diese Behauptung. Die ersten beiden Formen führen normalerweise zu besser lesbarem Code… (S. 239)

In einer zeitgemäßen, flüssigeren API würde ich das erwarten

Heading reciprocalHeading = myHeading.reciprocal();

- was wiederum darauf hindeuten würde, dass der Anruf billig und idempotent ist und keine Nebenwirkungen aufweist, aber nichts darüber aussagt, ob eine neue Berechnung durchgeführt oder ein neues Objekt erstellt wird. Das ist in Ordnung; In einer guten API sollte es mich nicht interessieren.

In Ruby gibt es keine Eigenschaft. Es gibt "Attribute", aber wenn ich sehe

reciprocalHeading = my_heading.reciprocal

Ich kann nicht sofort feststellen, ob ich über eine @reciprocal Oder eine einfache Zugriffsmethode auf eine Instanzvariable attr_reader Zugreife oder ob ich eine Methode aufrufe, die eine teure Berechnung durchführt. Die Tatsache, dass der Methodenname ein einfaches Substantiv ist, deutet jedoch darauf hin, dass der Aufruf zumindest billig ist und wahrscheinlich keine Nebenwirkungen hat, anstatt calcReciprocal zu sagen.

In Scala lautet die Namenskonvention, dass Methoden mit Nebenwirkungen Klammern und Methoden ohne diese Klammern verwenden, dies jedoch nicht

val reciprocal = heading.reciprocal

könnte eines von folgenden sein:

// immutable public value initialized at creation time
val reciprocal: Heading = … 

// immutable public value initialized on first use
lazy val reciprocal: Heading = … 

// public method, probably recalculating on each invocation
def reciprocal: Heading = …

// as above, with parentheses that, by convention, the caller
// should only omit if they know the method has no side effects
def reciprocal(): Heading = …

(Beachten Sie, dass Scala erlaubt verschiedene Dinge, die dennoch entmutigt werden vom Styleguide . Dies ist einer meiner größten Ärger mit Scala.)

Das Fehlen von Klammern zeigt an, dass der Anruf keine Nebenwirkungen hat. Der Name deutet wiederum darauf hin, dass der Anruf relativ billig sein sollte. Darüber hinaus ist mir egal wie es mir den Wert bringt.

Kurz gesagt: Kennen Sie die Sprache, die Sie verwenden, und wissen Sie, welche Erwartungen andere Programmierer an Ihre API stellen werden. Alles andere ist ein Implementierungsdetail.

5
David Moles

Wie andere gesagt haben, ist es ein weit verbreitetes Muster, Instanzen derselben Klasse zurückzugeben.

Die Benennung sollte mit den Namenskonventionen der Sprache Hand in Hand gehen.

Zum Beispiel würde ich in Java wahrscheinlich erwarten, dass es getReciprocal(); heißt

Davon abgesehen würde ich veränderlich gegen unveränderlich Objekte in Betracht ziehen.

Mit nveränderlich sind die Dinge ziemlich einfach und es tut nicht weh, wenn Sie dasselbe Objekt zurückgeben oder nicht.

Mit veränderlich kann dies sehr beängstigend werden.

b = a.reciprocal
a += 1
b = what?

Worauf bezieht sich b nun? Der Kehrwert des ursprünglichen Wertes von a? Oder das Gegenteil des Veränderten? Dies mag ein zu kurzes Beispiel sein, aber Sie verstehen es.

In diesen Fällen suchen Sie nach einer besseren Benennung , die kommuniziert, was passiert, zum Beispiel könnte createReciprocal() eine bessere Wahl sein.

Aber es kommt auch wirklich auf den Kontext an.

2
Eiko

Im Interesse der Einzelverantwortung und Klarheit hätte ich eine ReverseHeadingFactory, die das Objekt nahm und seine Rückseite zurückgab. Dies würde klarer machen, dass ein neues Objekt zurückgegeben wird, und würde bedeuten, dass der Code zur Erzeugung der Umkehrung von anderem Code weg gekapselt wurde.

1
matt freake

Es hängt davon ab, ob. Normalerweise erwarte ich, dass Eigenschaften Dinge zurückgeben, die Teil einer Instanz sind, daher wäre es etwas ungewöhnlich, eine andere Instanz derselben Klasse zurückzugeben.

Beispielsweise könnte eine Zeichenfolgenklasse die Eigenschaften "firstWord", "lastWord" oder Unicode "firstLetter" oder "lastLetter" haben, bei denen es sich um vollständige Zeichenfolgenobjekte handelt - normalerweise nur um kleinere.

0
gnasher729

Da die anderen Antworten den Eigenschaftsteil abdecken, werde ich nur Ihre Nummer 2 über reciprocal ansprechen:

Verwenden Sie nichts anderes als reciprocal. Es ist der einzig richtige Begriff für das, was Sie beschreiben (und es ist der formale Begriff). Schreiben Sie keine falsche Software, um Ihre Entwickler vor dem Erlernen der Domäne zu bewahren, in der sie arbeiten. In der Navigation haben Begriffe eine sehr spezifische Bedeutung und verwenden etwas, das so harmlos erscheint, wie es reverse oder opposite führen könnte Verwirrung in bestimmten Zusammenhängen.

0
Shaz

Logischerweise ist es keine Eigenschaft einer Überschrift. Wenn es so wäre, könnte man auch sagen, dass das Negativ einer Zahl eine Eigenschaft dieser Zahl ist, und ehrlich gesagt würde "Eigentum" jede Bedeutung verlieren, fast alles wäre eine Eigenschaft.

Reziprok ist eine reine Funktion einer Überschrift, genauso wie das Negative einer Zahl eine reine Funktion dieser Zahl ist.

Als Faustregel gilt: Wenn die Einstellung nicht sinnvoll ist, sollte sie wahrscheinlich keine Eigenschaft sein. Das Einstellen kann natürlich immer noch nicht erlaubt sein, aber das Hinzufügen eines Setters sollte theoretisch möglich und sinnvoll sein.

In einigen Sprachen kann es sinnvoll sein, es als eine Eigenschaft zu haben, die im Kontext dieser Sprache angegeben ist, wenn dies aufgrund der Mechanik dieser Sprache einige Vorteile bringt. Wenn Sie es beispielsweise in einer Datenbank speichern möchten, ist es wahrscheinlich sinnvoll, es als Eigenschaft festzulegen, wenn es die automatische Verarbeitung durch das ORM-Framework ermöglicht. Oder vielleicht ist es eine Sprache, in der es keinen Unterschied zwischen einer Eigenschaft und einer parameterlosen Elementfunktion gibt (ich kenne eine solche Sprache nicht, aber ich bin sicher, dass es einige gibt). Dann ist es Sache des API-Designers und des Dokumenters, zwischen Funktionen und Eigenschaften zu unterscheiden.


Bearbeiten: Speziell für C # würde ich vorhandene Klassen betrachten, insbesondere die der Standardbibliothek.

  • Es gibt BigInteger und Complex Strukturen. Aber sie sind Strukturen und haben nur statische Funktionen, und alle Eigenschaften sind von unterschiedlichem Typ. Es gibt also nicht viel Designhilfe für Ihre Heading Klasse.
  • Math.NET Numerics hat Vector class , was nur sehr wenige Eigenschaften hat und Ihrem Heading ziemlich nahe zu sein scheint. Wenn Sie dies tun, haben Sie eine Funktion für das Gegenteil, keine Eigenschaft.

Möglicherweise möchten Sie sich Bibliotheken ansehen, die tatsächlich von Ihrem Code verwendet werden, oder vorhandene ähnliche Klassen. Versuchen Sie, konsequent zu bleiben, das ist normalerweise das Beste, wenn ein Weg nicht eindeutig richtig und ein anderer falsch ist.

0
hyde