クラスのメソッドは、それ自体を変更した後、いつ同じインスタンスを返す必要がありますか?


9

私は3つのメソッドを持つクラスを持っているA()B()C()。これらのメソッドは、独自のインスタンスを変更します。

インスタンスが別のコピーである場合(同様に)、メソッドはインスタンスClone()を返すvoid必要がありreturn this;ますが、メソッドで同じインスタンスを変更し、他の値を返さない場合は、同じインスタンス()を返すか、自由に選択できます。

同じ変更されたインスタンスを返すことを決定するとき、のようなきちんとしたメソッドチェーンを実行できますobj.A().B().C();

これがそうする唯一の理由でしょうか?

自分のインスタンスを変更して返すこともできますか?それとも、コピーのみを返し、元のオブジェクトを以前のままにしておくべきですか?同じ変更されたインスタンスを返す場合、ユーザーは戻り値がコピーであると想定する可能性があるため、それ以外の場合は返されませんか?それが問題なければ、メソッドでそのようなことを明確にする最良の方法は何ですか?

回答:


7

連鎖を使用する場合

関数チェーンは、オートコンプリートを備えたIDEが一般的な場所である言語で主に人気があります。たとえば、ほとんどすべてのC#開発者がVisual Studioを使用しています。したがって、C#を使用して開発している場合、メソッドにチェーンを追加すると、Visual Studioがチェーンの構築を支援するため、そのクラスのユーザーの時間を節約できます。

一方、PHPのような本質的に非常に動的で、IDEでオートコンプリートをサポートしていない言語では、連鎖をサポートするクラスが少なくなります。連鎖は、正しいphpDocsを使用して連鎖可能なメソッドを公開する場合にのみ適切です。

連鎖とは何ですか?

Foo次の2つのメソッドという名前のクラスを指定すると、どちらもチェーン可能です。

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

1つが現在のインスタンスへの参照であり、新しいインスタンスを作成するという事実は、これらがチェーン可能なメソッドであることを変更しません。

連鎖可能なメソッドは現在のオブジェクトのみを参照する必要があるという絶対的なルールはありません。実際、連鎖可能なメソッドは2つの異なるクラスにまたがることがあります。例えば;

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

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

this上記の例のいずれにも参照はありません。コードa.What().When()はチェーンの例です。興味深いのは、クラス型Bが変数に割り当てられないことです。

メソッドは、その戻り値が式の次のコンポーネントとして使用されるようになると連鎖します。

ここにいくつかの例があります

 // 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();

ときに使用するthisと、new(this)

ほとんどの言語の文字列は不変です。したがって、メソッド呼び出しをチェーンすると、常に新しい文字列が作成されます。StringBuilderのようなオブジェクトを変更できる場所。

一貫性がベストプラクティスです。

オブジェクトの状態を変更してを返すメソッドがある場合は、this新しいインスタンスを返すメソッドを混在させないでください。代わりに、Clone()これを明示的に行うという特定のメソッドを作成します。

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

内部で何が起こっているかについては、それがはるかに明確aです。

外のステップと裏技

これをステップアウトおよびバックトリック呼びます。これは、チェーンに関連する一般的な問題の多くを解決するためです。これは基本的に、元のクラスから新しい一時クラスに降りてから、元のクラスに戻ることを意味します。

一時クラスは、元のクラスに特別な機能を提供するためだけに存在しますが、特別な条件下でのみ存在します。

チェーンが状態を変更する必要がある場合がよくありますが、クラスAはこれらすべての可能な状態を表すことができません。したがって、チェーン中に、への参照を含む新しいクラスが導入されますA。これにより、プログラマーは状態にステップインしてに戻ることができAます。

これが私の例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();

これは非常に単純な例です。より詳細に制御したい場合は、さまざまなメソッドを持つB新しいスーパータイプに戻ることができAます。


4
Intellisenseのようなオートコンプリートサポートのみに依存するメソッドチェーンを推奨する理由が本当にわかりません。メソッドチェーンまたはFluentインターフェイスは、あらゆるOOP言語のAPIデザインパターンです。オートコンプリートが行う唯一のことは、タイプミスやタイプエラーを防ぐことです。
アモン

@amonあなたは私が言ったことを間違って読んだ。ある言語でインテリセンスが一般的である場合、より人気があると私は言った。それが機能に依存しているとは決して言いませんでした。
Reactgular 2014

3
この回答は、連鎖の可能性を理解するのに大いに役立ちますが、それでも、連鎖を使用するタイミングを決定することはできません。確かに、一貫性は最高です。ただし、私は関数とでオブジェクトを変更する場合について言及していますreturn this。ライブラリのユーザーがこの状況を処理して理解するのをどのように支援できますか?(それが必要でない場合でも、メソッドをチェーンすることを許可しますが、これは本当に大丈夫ですか?または私は一方向のみに固執する必要がありますか、まったく変更が必要ですか?)
Martin Braun

@modiX私の答えが正しければ、それを答えとして受け入れてください。あなたが探している他のものは、チェーンを使用する方法についての意見であり、そのための正しい/間違った答えはありません。それは本当にあなた次第です。たぶん、細かい部分について心配しすぎているのでしょうか?
Reactgular

3

これらのメソッドは、独自のインスタンスを変更します。

リターンというメソッドを持つ、言語に応じてvoid/ unitと変更そのインスタンスまたはパラメータが非慣用的です。以前はそれ以上のことを行っていた言語(C#、C ++)でも、より機能的なスタイルのプログラミング(不変オブジェクト、純粋な関数)へのシフトにより時代遅れになっています。しかし、今のところ正当な理由があると仮定しましょう。

これがそうする唯一の理由でしょうか?

特定の動作(と考えるx++)では、変数を変更しても、操作が結果を返すことが期待されます。しかし、それが主に自分で行う唯一の理由です。

自分のインスタンスを変更して返すこともできますか?それとも、コピーのみを返し、元のオブジェクトを以前のままにしておくべきですか?同じ変更されたインスタンスを返す場合、ユーザーは戻り値がコピーであることを認める可能性があるため、それ以外の場合は返されませんか?それが問題なければ、メソッドでそのようなことを明確にする最良の方法は何ですか?

場合によります。

コピー/新規作成とリターンが一般的な言語(C#LINQと文字列)では、同じ参照を返すと混乱を招きます。変更と復帰が一般的な言語(一部のC ++ライブラリ)では、コピーは混乱を招きます。

void明確にするための最良の方法は、(プロパティのような言語構造を返すか使用することによって)署名を明確にすることです。その後SetFoo、既存のインスタンスを変更していることを示すように名前を明確にすることは良いことです。ただし、重要なのは、使用している言語/ライブラリのイディオムを維持することです。


あなたの答えのためのThx。私は主に.NETフレームワークを使用していますが、あなたの答えでは、それは慣用的ではないものでいっぱいです。LINQメソッドのようなものは、操作などを追加した後、元の新しいコピーを返します。Clear()またはまたはAdd()任意のコレクション型のものは、同じインスタンスを変更して返しvoidます。どちらの場合も、混乱を招くと言います...
Martin Braun 14

@modix-LINQは、操作の追加中にコピーを返しませ
Telastyn 2014

なんでそんなことができるのobj = myCollection.Where(x => x.MyProperty > 0).OrderBy(x => x.MyProperty);Where()新しいコレクションオブジェクトを返します。OrderBy()コンテンツが順序付けられているため、別のコレクションオブジェクトも返します。
マーティンブラウン

1
@modix -彼らは、実装する新しいオブジェクトを返しIEnumerableますが、明確にするために、彼らはコピーではない、と彼らは(以外のコレクションではありませんToListToArrayなど)。
Telastyn 2014

これは正しい、それらはコピーではなく、さらにそれらは確かに新しいオブジェクトです。また、コレクションとは、から継承するクラスではなく、カウントできるオブジェクト(任意の種類の配列に複数のオブジェクトを保持するオブジェクト)を指していると言いますICollection。私の悪い。
Martin Braun

0

(私はあなたのプログラミング言語としてC ++を想定しました)

私にとって、これは主に可読性の側面です。A、B、Cが修飾子の場合、特にこれがパラメータとして関数に渡される一時的なオブジェクトの場合、たとえば

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

に比べ

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

変更されたインスタンスへの参照を返すのに問題がない場合は、「はい」と言って、たとえば、ストリーム演算子「>>」と「<<」(http://www.cprogramming.com/tutorial/)を示します。 operator_overloading.html


1
あなたは間違っていると思いました、それはC#です。しかし、私は私の質問をより一般的にしたいです。
マーティンブラウン

もちろん、Javaの可能性もありましたが、これは、演算子を使用した私の例をコンテキストに入れるための小さな点でした。本質は、それが読みやすさに帰着することであり、それは読者の期待と背景に影響され、それ自体が特定の言語/フレームワークの通常の実践に影響されます-他の回答も指摘しているように。
AdrianI 2014

0

また、メソッドリターンを変更するのではなく、コピーリターンで処理をチェーンすることもできます。

良いC#の例はstring.Replace(a、b)です。これは、それが呼び出された文字列を変更しませんが、代わりに新しい文字列を返します。


はい、知っています。しかし、とにかく自分のインスタンスを編集したくないときにこのケースを使わなければならないので、私はこのケースを参照したくありませんでした。ただし、指摘いただきありがとうございます。
マーティンブラウン

他の答えによれば、セマンティクスの一貫性は重要です。コピーリターンと一貫性があり、変更リターンと一貫性がない(不変オブジェクトでは実行できないため)ため、質問に対する答えはコピーリターンを使用しないことです。
Peter Wone 2014
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.