Wann sollte eine Methode einer Klasse dieselbe Instanz zurückgeben, nachdem sie sich selbst geändert hat?

9

Ich habe eine Klasse, die drei Methoden hat A(), B()und C(). Diese Methoden ändern die eigene Instanz.

Während die Methoden eine Instanz zurückgeben müssen, wenn die Instanz eine separate Kopie ist (genau wie Clone()), habe ich die freie Wahl, voiddieselbe Instanz ( return this;) zurückzugeben, wenn dieselbe Instanz in der Methode geändert wird und kein anderer Wert zurückgegeben wird.

Wenn ich mich für die Rückgabe derselben modifizierten Instanz entscheide, kann ich ordentliche Methodenketten wie ausführen obj.A().B().C();.

Wäre dies der einzige Grund dafür?

Ist es überhaupt in Ordnung, die eigene Instanz zu ändern und auch zurückzugeben? Oder sollte es nur eine Kopie zurückgeben und das Originalobjekt wie zuvor belassen? Denn wenn der Benutzer dieselbe geänderte Instanz zurückgibt, würde er möglicherweise annehmen, dass der zurückgegebene Wert eine Kopie ist, andernfalls würde er nicht zurückgegeben? Wenn es in Ordnung ist, was ist der beste Weg, um solche Dinge über die Methode zu klären?

Martin Braun
quelle

Antworten:

7

Wann wird die Verkettung verwendet?

Funktionsverkettung ist vor allem in Sprachen beliebt, in denen eine IDE mit automatischer Vervollständigung üblich ist. Beispielsweise verwenden fast alle C # -Entwickler Visual Studio. Wenn Sie mit C # entwickeln, kann das Hinzufügen von Verkettungen zu Ihren Methoden für Benutzer dieser Klasse Zeit sparen, da Visual Studio Sie beim Erstellen der Kette unterstützt.

Auf der anderen Seite werden Sprachen wie PHP, die von Natur aus sehr dynamisch sind und in IDEs häufig keine automatische Vervollständigung unterstützen, weniger Klassen sehen, die die Verkettung unterstützen. Eine Verkettung ist nur dann angebracht, wenn korrekte phpDocs verwendet werden, um die verkettbaren Methoden freizulegen.

Was ist Verkettung?

Bei einer Klasse mit dem Namen sind Foodie folgenden beiden Methoden verkettbar.

function what() { return this; }
function when() { return new Foo(this); }

Die Tatsache, dass man auf die aktuelle Instanz verweist und eine neue Instanz erstellt, ändert nichts daran, dass es sich um verkettbare Methoden handelt.

Es gibt keine Goldregel, dass eine verkettbare Methode nur auf das aktuelle Objekt verweisen darf. Tatsächlich können verkettbare Methoden zwei verschiedene Klassen umfassen. Beispielsweise;

class B { function When() { return true; } };
class A { function What() { return new B(); } };

var a = new A();
var x = a.What().When();

thisIn keinem der obigen Beispiele wird darauf verwiesen . Der Code a.What().When()ist ein Beispiel für eine Verkettung. Interessant ist, dass der Klassentyp Bniemals einer Variablen zugewiesen wird.

Eine Methode wird verkettet, wenn ihr Rückgabewert als nächste Komponente eines Ausdrucks verwendet wird.

Hier sind einige weitere Beispiele

 // return value never assigned.
 myFile.Open("something.txt").Write("stuff").Close();

// two chains used in expression
int x = a.X().Y() * b.X().Y();

// a chain that creates new strings
string name = str.Substring(1,10).Trim().ToUpperCase();

Wann zu verwenden thisundnew(this)

Zeichenfolgen in den meisten Sprachen sind unveränderlich. Verkettungsmethodenaufrufe führen also immer dazu, dass neue Zeichenfolgen erstellt werden. Wobei als Objekt wie StringBuilder geändert werden kann.

Konsistenz ist eine bewährte Methode.

Wenn Sie Methoden haben, die den Status eines Objekts ändern und zurückgeben this, mischen Sie keine Methoden ein, die neue Instanzen zurückgeben. Erstellen Sie stattdessen eine bestimmte Methode mit dem Namen Clone(), die dies explizit ausführt.

 var x  = a.Foo().Boo().Clone().Foo();

Das ist viel klarer, was im Inneren vor sich geht a.

Der Schritt nach draußen und zurück Trick

Ich nenne dies den Step-out- und Back- Trick, weil er viele häufige Probleme im Zusammenhang mit der Verkettung löst. Dies bedeutet im Grunde, dass Sie aus der ursprünglichen Klasse in eine neue temporäre Klasse und dann zurück in die ursprüngliche Klasse wechseln.

Die temporäre Klasse existiert nur, um der ursprünglichen Klasse spezielle Funktionen bereitzustellen, jedoch nur unter besonderen Bedingungen.

Es gibt oft Zeiten, in denen eine Kette den Status ändern muss , aber die Klasse Akann nicht alle diese möglichen Zustände darstellen . Während einer Kette wird eine neue Klasse eingeführt, die einen Verweis auf enthält A. Dies ermöglicht es dem Programmierer, in einen Zustand und zurück zu wechseln A.

Hier ist mein Beispiel, lassen Sie den Sonderzustand bekannt sein als B.

class A {
    function Foo() { return this; }
    function Boo() { return this; }
    function Change() return new B(this); }
}

class B {
    var _a;
    function (A) { _a = A; }
    function What() { return this; }
    function When() { return this; }
    function End() { return _a; }
}

var a = new A();
a.Foo().Change().What().When().End().Boo();

Das ist ein sehr einfaches Beispiel. Wenn Sie mehr Kontrolle haben möchten, Bkönnen Sie zu einem neuen Supertyp zurückkehren A, der andere Methoden hat.

Reactgular
quelle
4
Ich verstehe wirklich nicht, warum Sie eine Methodenverkettung empfehlen, die ausschließlich von der Unterstützung der Intellisense-ähnlichen automatischen Vervollständigung abhängt. Methodenketten oder fließende Schnittstellen sind ein API-Entwurfsmuster für jede OOP-Sprache. Das einzige, was die automatische Vervollständigung bewirkt, ist das Verhindern von Tippfehlern und Tippfehlern.
Amon
@amon du hast falsch verstanden was ich gesagt habe. Ich sagte, es ist populärer, wenn Intellisense für eine Sprache üblich ist. Ich habe nie gesagt, dass es von der Funktion abhängt.
Reactgular
3
Obwohl diese Antwort mir sehr hilft, die Möglichkeiten der Verkettung zu verstehen, fehlt mir immer noch die Entscheidung, wann ich die Verkettung überhaupt verwenden soll. Sicher, Konsistenz ist das Beste . Ich beziehe mich jedoch auf den Fall, wenn ich mein Objekt in der Funktion und ändere return this. Wie kann ich den Benutzern meiner Bibliotheken helfen, mit dieser Situation umzugehen und sie zu verstehen? (Es wäre wirklich in Ordnung, ihnen zu erlauben, Methoden zu verketten, auch wenn dies nicht erforderlich ist. Oder sollte ich mich nur an einen Weg halten und überhaupt Änderungen vornehmen?)
Martin Braun
@modiX Wenn meine Antwort korrekt ist, akzeptieren Sie sie bitte als Antwort. Die anderen Dinge, die Sie suchen, sind Meinungen zur Verwendung von Verkettung, und es gibt keine richtige / falsche Antwort darauf. Das liegt wirklich an Ihnen zu entscheiden. Vielleicht sorgen Sie sich zu sehr um kleine Details?
Reactgular
3

Diese Methoden ändern die eigene Instanz.

Abhängig von der Sprache ist es nicht idiomatisch , Methoden zu haben, die ihre Instanz oder Parameter zurückgeben void/ unitund ändern. Selbst in Sprachen, die dies früher mehr taten (C #, C ++), gerät es mit der Verlagerung hin zu einer funktionaleren Stilprogrammierung (unveränderliche Objekte, reine Funktionen) aus der Mode. Aber nehmen wir an, es gibt einen guten Grund für jetzt.

Wäre dies der einzige Grund dafür?

Für bestimmte Verhaltensweisen (denken Sie x++) wird erwartet, dass die Operation das Ergebnis zurückgibt, obwohl sie die Variable ändert. Aber das ist größtenteils der einzige Grund, es selbst zu tun.

Ist es überhaupt in Ordnung, die eigene Instanz zu ändern und auch zurückzugeben? Oder sollte es nur eine Kopie zurückgeben und das Originalobjekt wie zuvor belassen? Denn wenn der Benutzer dieselbe geänderte Instanz zurückgibt, würde er möglicherweise zugeben, dass der zurückgegebene Wert eine Kopie ist, andernfalls würde er nicht zurückgegeben? Wenn es in Ordnung ist, was ist der beste Weg, um solche Dinge über die Methode zu klären?

Es hängt davon ab, ob.

In Sprachen, in denen copy / new und return üblich sind (C # LINQ und Strings), wäre die Rückgabe derselben Referenz verwirrend. In Sprachen, in denen Änderungen und Rückgaben üblich sind (einige C ++ - Bibliotheken), wäre das Kopieren verwirrend.

Die Signatur eindeutig zu machen (durch Rückgabe voidoder Verwendung von Sprachkonstrukten wie Eigenschaften), wäre der beste Weg, dies zu klären. Danach ist es gut, den Namen klar SetFoozu machen, um zu zeigen, dass Sie die vorhandene Instanz ändern. Der Schlüssel ist jedoch, die Redewendungen der Sprache / Bibliothek beizubehalten, mit der Sie arbeiten.

Telastyn
quelle
Danke für deine Antwort. Ich arbeite hauptsächlich mit dem .NET Framework und Ihrer Antwort nach ist es voller nicht-idiomatischer Dinge. Während Dinge wie LINQ-Methoden nach dem Anhängen von Vorgängen usw. eine neue Kopie des Originals zurückgeben, ändern Dinge wie der Clear()oder Add()eines beliebigen Sammlungstyps dieselbe Instanz und geben sie zurück void. In beiden Fällen sagen Sie, es wäre verwirrend ...
Martin Braun
@modix - LINQ gibt beim Anhängen von Vorgängen keine Kopie zurück.
Telastyn
Warum kann ich obj = myCollection.Where(x => x.MyProperty > 0).OrderBy(x => x.MyProperty);dann tun ? Where()Gibt ein neues Sammlungsobjekt zurück. OrderBy()gibt sogar ein anderes Sammlungsobjekt zurück, da der Inhalt sortiert ist.
Martin Braun
1
@modix - Sie kehren neue Objekte , die Umsetzung IEnumerable, aber klar sein, sie sind keine Kopien, und sie sind nicht Sammlungen ( mit Ausnahme ToList, ToArrayusw.).
Telastyn
Das ist richtig, sie sind keine Kopien, außerdem sind sie in der Tat neue Objekte. Indem ich Sammlung sagte, bezog ich mich auch auf Objekte, durch die man zählen kann (Objekte, die mehr als ein Objekt in einem Array enthalten), nicht auf Klassen, von denen geerbt wird ICollection. Mein Fehler.
Martin Braun
0

(Ich habe C ++ als Ihre Programmiersprache angenommen)

Für mich ist dies hauptsächlich ein Aspekt der Lesbarkeit. Wenn A, B, C Modifikatoren sind, insbesondere wenn dies ein temporäres Objekt ist, das als Parameter an eine Funktion übergeben wird, z

do_something(
      CPerson('name').age(24).height(160).weight(70)
      );

verglichen mit

{
   CPerson person('name');
   person.set_age(24);
   person.set_height(160);
   person.set_weight(70);
   do_something(person);
}

Wenn es in Ordnung ist, einen Verweis auf die geänderte Instanz zurückzugeben, würde ich ja sagen und Sie beispielsweise auf die Stream-Operatoren '>>' und '<<' verweisen ( http://www.cprogramming.com/tutorial/). operator_overloading.html )

AdrianI
quelle
1
Sie haben falsch angenommen, es ist C #. Ich möchte jedoch, dass meine Frage häufiger gestellt wird.
Martin Braun
Sicher, hätte auch Java sein können, aber das war ein kleiner Punkt, nur um mein Beispiel mit den Operatoren in einen Kontext zu stellen. Das Wesentliche war, dass es auf die Lesbarkeit ankommt, die von den Erwartungen und dem Hintergrund des Lesers beeinflusst wird, die selbst von den üblichen Praktiken in der jeweiligen Sprache / den jeweiligen Frameworks beeinflusst werden - wie auch die anderen Antworten gezeigt haben.
AdrianI
0

Sie können die Methode auch mit der Kopierrückgabe verketten, anstatt die Rückgabe zu ändern.

Ein gutes C # -Beispiel ist string.Replace (a, b), das die Zeichenfolge, für die es aufgerufen wurde, nicht ändert, sondern stattdessen eine neue Zeichenfolge zurückgibt, mit der Sie fröhlich verketten können.

Peter Wone
quelle
Ja, ich weiß. Ich wollte mich aber nicht auf diesen Fall beziehen, da ich diesen Fall verwenden muss, wenn ich die eigene Instanz sowieso nicht bearbeiten möchte. Vielen Dank jedoch für den Hinweis.
Martin Braun
Bei anderen Antworten ist die Konsistenz der Semantik wichtig. Da Sie mit der Rückgabe von Kopien konsistent sein können und nicht mit der Rückgabe von Änderungen übereinstimmen können (weil Sie dies nicht mit unveränderlichen Objekten tun können), würde ich sagen, dass die Antwort auf Ihre Frage lautet, keine Rückgabe von Kopien zu verwenden.
Peter Wone