メソッドの連鎖-なぜそれが良い習慣なのか、そうでないのか?


151

メソッドチェーンは、別のメソッドに対して結果を呼び出すためにオブジェクト自体を返すオブジェクトメソッドの慣行です。このような:

participant.addSchedule(events[1]).addSchedule(events[2]).setStatus('attending').save()

可読コード、または「滑らかなインターフェース」を生成するため、これは良い習慣と考えられています。ただし、私にとっては、オブジェクト指向自体が意味するオブジェクト呼び出し表記が壊れているように見えます。結果のコードは、前のメソッドの結果に対するアクションの実行を表していないため、通常、オブジェクト指向コードが機能すると予想されます。

participant.getSchedule('monday').saveTo('monnday.file')

この違いにより、「結果のオブジェクトを呼び出す」というドット表記の2つの異なる意味が作成されます。チェーンのコンテキストでは、上記の例は実際にはスケジュールを保存することを目的としていますが、参加者オブジェクトの保存として読み取られます。 getScheduleが受け取ったオブジェクト。

ここでの違いは、呼び出されたメソッドが何かを返すことを期待する必要があるかどうかです(この場合、呼び出されたオブジェクト自体がチェーンに返されます)。ただし、これらの2つのケースは、表記法自体と区別できません。呼び出されるメソッドのセマンティクスのみから区別できます。メソッドチェーンが使用されていない場合、メソッド呼び出しが前の呼び出しの結果に関連する何かに作用することを常に知ることができます-チェーンを使用すると、この仮定が破られ、実際のオブジェクトが何であるかを理解するためにチェーン全体を意味的に処理する必要があります本当に呼ばれています。例えば:

participant.attend(event).setNotifications('silent').getSocialStream('twitter').postStatus('Joining '+event.name).follow(event.getSocialId('twitter'))

ここで、最後の2つのメソッド呼び出しはgetSocialStreamの結果を参照していますが、前のメソッド呼び出しは参加者を参照しています。コンテキストが変化するチェーンを実際に作成することは悪い習慣かもしれません(そうですか?) 。

私には、メソッドチェーンが表面的には読み取り可能なコードを生成する一方で、ドット表記の意味をオーバーロードすると、さらに混乱が生じるだけのようです。私はプログラミングの第一人者とは思わないので、私が犯したのは自分のせいだと思います。だから:私は何が欠けていますか?どういうわけか間違った方法チェーンを理解していますか?メソッドチェーンが特に優れているケースや、特に悪いケースはありますか?

補足:この質問は、質問として隠された意見の声明として読むことができることを理解しています。ただし、そうではありません-チェーンが優れたプラクティスと見なされている理由を理解したいのですが、それが本来のオブジェクト指向表記を壊すと考えるときにどこが間違っているのでしょうか。


メソッドチェーンは、Javaでスマートコードを記述するための少なくとも1つの方法であると思われます。全員が同意するわけではありませんが..
マーサートライエスト2009

また、これらの「流暢な」メソッドまたは「流暢な」インターフェースを呼び出します。この用語を使用するようにタイトルを更新することができます。
S.Lott、2009

4
別のSOの議論では、流暢なインターフェイスはコードの可読性に関係するより大きな概念であり、メソッドチェーンはこれに向けた1つの方法に過ぎないと言われました。ただし、これらは密接に関連しているので、タグと参照される流暢なインターフェイスをテキストに追加しました。これらで十分だと思います。
Ilari Kajaste、2009

14
私がこれについて考えるようになったのは、メソッドチェーンは、実際には、不足している機能を言語構文に取り込むためのハッキングです。.メソッドの戻り値を無視し、常に同じオブジェクトを使用してチェーンされたメソッドを呼び出す組み込みの代替表記法がある場合は、実際には必要ありません。
Ilari Kajaste 2011

これは優れたコーディングイディオムですが、他のすべての優れたツールと同様に悪用されます。
Martin Spamer 2013年

回答:


74

これは主観的であることに同意します。ほとんどの場合、メソッドチェーンを回避しますが、最近はそれがちょうどいい場合も見つけました。10個のパラメーターなどを受け入れるメソッドがあり、それ以上必要ですが、ほとんどの場合、指定する必要がありました。少ない。オーバーライドにより、これは非常に扱いにくく、非常に速くなりました。代わりに、チェーンアプローチを選択しました。

MyObject.Start()
    .SpecifySomeParameter(asdasd)
    .SpecifySomeOtherParameter(asdasd)
    .Execute();

メソッドチェーンアプローチはオプションでしたが、コードの記述が簡単になりました(特にIntelliSenseを使用)。ただし、これは1つの孤立したケースであり、私のコードでは一般的な方法ではありません。

重要なのは-99%のケースでは、メソッドチェーンを使用しなくても、おそらく同じかそれ以上に実行できるということです。しかし、これが最善のアプローチである1%があります。


4
IMO、このシナリオでメソッドチェーンを使用する最良の方法は、などの関数に渡されるパラメーターオブジェクトを作成することP = MyObject.GetParamsObj().SomeParameter(asdasd).SomeOtherParameter(asdasd); Obj = MyObject.Start(); MyObject.Execute(P);です。このパラメーターオブジェクトを他の呼び出しで再利用できるという利点があります。これはプラスです。
pedromanoel

20
パターンに関する私の貢献のちょうど1セント、ファクトリーメソッドは通常1つの作成ポイントのみを持ち、製品はファクトリーメソッドのパラメーターに基づいて静的に選択されます。チェーンの作成は、結果を取得するためにさまざまなメソッドを呼び出すビルダーパターンに似ています。メソッドチェーンのように、メソッドはオプションにすることができます。ここではPizzaBuilder.AddSauce().AddDough().AddTopping()より多くの参照のようなものを作成できます
Marco Medrano

3
メソッドチェーニング(元の質問のchownとして)は、デメテルの法則に違反している場合は悪いと見なされます。参照:ifacethoughts.net/2006/03/07/…ここでの回答は、実際には「ビルダーパターン」であるため、その法律に従います。
Angel O'Sphere 14

2
@Marco Medrano PizzaBuilderの例は、JavaWorldで何年も前に読んだので、いつも私を悩ませました。シェフではなくピザにソースを追加するべきだと思います。
–BreandánDalton 2017

1
Vilx、私はあなたの意味を知っています。しかし、私がを読んだときlist.add(someItem)、それを「このコードはオブジェクトに追加someItemしている」と読みましたlist。したがって、を読んだときPizzaBuilder.AddSauce()、これは「このコードはPizzaBuilderオブジェクトにソースを追加している」と自然に読みます。つまり、オーケストレーター(追加を行うオーケストレーター)を、コードlist.add(someItem)またはPizzaBuilder.addSauce()が埋め込まれているメソッドと見なします。しかし、PizzaBuilderの例は少し人工的なものだと思います。あなたの例はMyObject.SpecifySomeParameter(asdasd)私にはうまくいきます。
–BreandánDalton 2017

78

ちょうど私の2セント。

メソッドチェーンを使用するとデバッグが難しくなります。-ブレークポイントを簡潔なポイントに配置できないため、プログラムを必要な場所に正確に一時停止できます-これらのメソッドの1つが例外をスローし、行番号が表示される場合は、わかりません「チェーン」内のどの方法で問題が発生したか。

一般的には、常に非常に短く簡潔な行を書くことをお勧めします。すべての行が1つのメソッド呼び出しを行うだけです。長い行よりも多くの行を優先します。

編集:コメントには、メソッドチェーンと改行が別々であると記載されています。それは本当です。ただし、デバッガによっては、ステートメントの途中にブレークポイントを配置できる場合とできない場合があります。可能な場合でも、中間変数を含む個別の行を使用すると、はるかに柔軟になり、デバッグプロセスに役立つ[ウォッチ]ウィンドウで調べることができる一連の値をすべて取得できます。


4
改行とメソッドチェーンを独立して使用していませんか?@ Vilx-の回答に従って、改行の各呼び出しと連鎖でき、通常は同じ行に複数の個別のステートメントを配置できます(Javaでセミコロンを使用するなど)。
ブラブスター2013年

2
これは完全に正当化された返信ですが、私が知っているすべてのデバッガーに存在する弱点を示しているだけで、特に質問とは関係ありません。
masterxilo 2014年

1
@ブラブスター。特定のデバッガーではそれらが個別になっている場合がありますが、中間変数を使用して個別の呼び出しを行うと、バグを調査する際にはるかに多くの情報を得ることができます。
RAY

4
+1、どの時点でも、メソッドチェーンのステップデバッグ時に各メソッドが何を返したかがわかりません。メソッドチェーンパターンはハックです。これは、メンテナンスプログラマにとって最悪の悪夢です。
Lee Kowalkowski、2015年

1
私もチェーンの大ファンではありませんが、メソッド定義内にブレークポイントを単に入れないのはなぜですか?
Pankaj Sharma

39

個人的には、複数のプロパティを設定したり、ユーティリティタイプのメソッドを呼び出したりするなど、元のオブジェクトにのみ作用するメソッドのチェーンを好みます。

foo.setHeight(100).setWidth(50).setColor('#ffffff');
foo.moveTo(100,100).highlight();

私の例では、1つ以上のチェーンされたメソッドがfoo以外のオブジェクトを返す場合は使用しません。チェーン内のそのオブジェクトに適切なAPIを使用している限り、構文的に何でもチェーンできますが、オブジェクトを変更すると、オブジェクトが読みにくくなり、異なるオブジェクトのAPIに類似性がある場合は、混乱を招く可能性があります。あなたが最後にいくつかの本当に一般的なメソッド呼び出しを行う場合は(.toString().print()、何でも)どのオブジェクトあなたは最終的に作用していますか?何気なくコードを読んでいる人は、コードが元の参照ではなく、チェーン内で暗黙的に返されるオブジェクトであることに気付かないかもしれません。

異なるオブジェクトをチェーンすると、予期しないnullエラーが発生する可能性もあります。私の例では、fooが有効であると仮定すると、すべてのメソッド呼び出しは「安全」です(たとえば、fooに対して有効)。OPの例では:

participant.getSchedule('monday').saveTo('monnday.file')

...外部の開発者がコードを調べている場合、getScheduleが実際にnull以外の有効なスケジュールオブジェクトを返すことは保証されません。また、多くのIDEはデバッグ時にメソッド呼び出しを検査可能なオブジェクトとして評価しないため、このスタイルのコードのデバッグは非常に困難な場合があります。IMO、デバッグの目的で検査するオブジェクトが必要になる場合はいつでも、明示的な変数に含めることをお勧めします。


Participant有効でない可能性がある場合SchedulegetScheduleメソッドはMaybe(of Schedule)型を返すように設計され、saveToメソッドはMaybe型を受け入れるように設計されています。
Lightman

24

Martin Fowlerはここで良い議論をしています:

メソッドの連鎖

いつ使うか

メソッドチェーンは、内部DSLの可読性を大幅に高める可能性があり、その結果、一部の人にとっては、内部DSLのほぼ同義語となっています。ただし、メソッドチェーンは、他の関数の組み合わせと組み合わせて使用​​する場合に最適です。

メソッドチェーンは、parent :: =(this | that)*のような文法で特に効果的です。さまざまなメソッドを使用すると、次に来る引数を確認するための読みやすい方法が提供されます。同様に、オプションの引数は、メソッドチェーンを使用して簡単にスキップできます。parent :: = first secondなどの必須の句のリストは、基本的な形式ではうまく機能しませんが、プログレッシブインターフェイスを使用することで十分にサポートできます。ほとんどの場合、その場合はネスト関数を使用します。

メソッドチェーンの最大の問題は、仕上げの問題です。回避策はありますが、通常、この問題が発生した場合は、ネストされた関数を使用した方がよいでしょう。ネストされた関数は、コンテキスト変数に混乱している場合にも適しています。


2
リンクは死んでいる:(
Qw3ry 2017

DSLとはどういう意味ですか?ドメイン固有言語
ソレン

@セーレン:ファウラーはドメイン固有の言語を参照します。
Dirk Vollmar

21

私の意見では、メソッドチェーンは少し目新しさです。もちろん、見た目はかっこいいですが、実際の利点はわかりません。

方法は:

someList.addObject("str1").addObject("str2").addObject("str3")

より良い:

someList.addObject("str1")
someList.addObject("str2")
someList.addObject("str3")

例外は、addObject()が新しいオブジェクトを返す場合である可能性があります。その場合、チェーン解除されたコードは、次のように少し面倒になる可能性があります。

someList = someList.addObject("str1")
someList = someList.addObject("str2")
someList = someList.addObject("str3")

9
最初の例でも「someList」の部分を2つ避け、3行ではなく1行になるので、より簡潔になります。それが実際に良いか悪いかは、さまざまなことに依存し、おそらく好みの問題です。
Fabian Steeg、

26
'someList'を1度だけ持つことの本当の利点は、長くてわかりやすい名前を付けることがはるかに簡単になることです。名前をすばやく連続して複数回表示する必要がある場合は常に、名前を短くする(繰り返しを減らし、読みやすさを向上させる)ため、説明が少なくなり、読みやすさが損なわれます。
Chris Dodd、

メソッドの連鎖が特定のメソッドの戻り値のイントロスペクションを防止し、チェーン内のすべてのメソッドからの空のリスト/セットおよび非null値の推定を暗示するという警告付きの可読性とDRY原則についての良い点(誤った原因システムの多くのNPEは、頻繁にデバッグ/修正する必要があります)。
ダレルティーグ

@ChrisDodd自動補完について聞いたことがない?
inf3rno

1
「人間の脳は、同じインデントを持っている場合、テキストの繰り返しを認識するのに非常に優れています」-それはまさに繰り返しの問題です。それは脳が繰り返しパターンに焦点を合わせるようにします。したがって、コードを読んで理解するには、繰り返しパターンの後ろ/後ろを見て、実際に何が起こっているかを確認する必要があります。これは、脳が見落とす傾向のある繰り返しパターンの違いです。これは、繰り返しがコードの可読性にとって非常に悪い理由です。
クリス・ドッド

7

呼び出しが別のクラスのインスタンスを返すように、予想よりも多くのオブジェクトに依存している可能性があるため、危険です。

例を挙げましょう:

foodStoreは、所有している多くのフードストアで構成されるオブジェクトです。foodstore.getLocalStore()は、パラメータに最も近い店舗の情報を保持するオブジェクトを返します。getPriceforProduct(anything)は、そのオブジェクトのメソッドです。

したがって、foodStore.getLocalStore(parameters).getPriceforProduct(anything)を呼び出すと

あなたはFoodStoreだけでなく、LocalStoreにも依存しています。

getPriceforProduct(anything)が変更された場合、FoodStoreだけでなく、チェーンメソッドを呼び出したクラスも変更する必要があります。

クラス間の疎結合を常に目指してください。

そうは言っても、私は個人的にはRubyをプログラミングするときにそれらを連鎖させるのが好きです。


7

多くの人は、読みやすさの問題を念頭に置くのではなく、便利な方法としてメソッドチェーンを使用しています。メソッドチェーンは、同じオブジェクトに対して同じアクションを実行する必要がある場合に受け入れられます。ただし、コードの記述を減らすだけでなく、実際に読みやすさが向上する場合に限ります。

残念ながら、質問で与えられた例のように、多くはメソッドチェーンを使用しています。それら依然として読み取り可能にできますが、残念ながら複数のクラス間で高い結合を引き起こしているため、望ましくありません。


6

これはちょっと主観的なようです。

メソッドの連鎖は、本質的に悪いものでも良いものでもありません。

読みやすさが最も重要です。

(また、多数のメソッドをチェーン化すると、何かが変更された場合に非常に脆弱になることを考慮してください)


それは確かに主観的かもしれません、それゆえ主観的なタグです。答えが私に強調してくれることを望んでいるのは、どのような場合にメソッドチェーンが良い考えであるかです。連鎖自体に本質的に悪い何か。
Ilari Kajaste 2009

それが高い結合をもたらすなら、それは本質的に悪いのではないでしょうか?チェーンを個々のステートメントに分割しても、読みやすさは低下しません。
aberrant80

1
あなたがどのようになりたいと思うかによります。それがより読みやすいものになるなら、これは多くの状況で好ましいかもしれません。このアプローチの大きな問題は、オブジェクトのほとんどのメソッドがオブジェクト自体への参照を返すことですが、多くの場合、メソッドはより多くのメソッドをチェーンできる子オブジェクトへの参照を返します。いったんこれを始めると、他のコーダーが何が起こっているのかを解くのが非常に難しくなります。また、メソッドの機能の変更は、大きな複合ステートメントでデバッグするのが面倒になります。
ジョンニコラス

6

チェーンの利点、
つまり、私がそれを使用したい場合

私が言及していないチェーンの1つの利点は、変数の開始時、または新しいオブジェクトをメソッドに渡すときにそれを使用できることです。これが悪い習慣かどうかはわかりません。

これは人為的な例であることは知っていますが、次のクラスがあるとします

Public Class Location
   Private _x As Integer = 15
   Private _y As Integer = 421513

   Public Function X() As Integer
      Return _x
   End Function
   Public Function X(ByVal value As Integer) As Location
      _x = value
      Return Me
   End Function

   Public Function Y() As Integer
      Return _y
   End Function
   Public Function Y(ByVal value As Integer) As Location
      _y = value
      Return Me
   End Function

   Public Overrides Function toString() As String
      Return String.Format("{0},{1}", _x, _y)
   End Function
End Class

Public Class HomeLocation
   Inherits Location

   Public Overrides Function toString() As String
      Return String.Format("Home Is at: {0},{1}", X(), Y())
   End Function
End Class

そして、基本クラスにアクセスできない、またはデフォルト値が時間に基づいて動的であると言います。はい、インスタンス化してから値を変更できますが、特に単に渡すだけの場合は面倒になる可能性がありますメソッドへの値:

  Dim loc As New HomeLocation()
  loc.X(1337)
  PrintLocation(loc)

しかし、これは非常に読みやすいだけではありません。

  PrintLocation(New HomeLocation().X(1337))

または、クラスのメンバーはどうですか?

Public Class Dummy
   Private _locA As New Location()
   Public Sub New()
      _locA.X(1337)
   End Sub
End Class

Public Class Dummy
   Private _locC As Location = New Location().X(1337)
End Class

これは私がチェーンを使用してきた方法であり、通常、私のメソッドは構成のためだけなので、長さは2行しかないため、値を設定してからReturn Meです。私たちにとっては、コードを読んで理解するのが非常に難しい巨大な行を、文のように読む1行にクリーンアップしました。何かのようなもの

New Dealer.CarPicker().Subaru.WRX.SixSpeed.TurboCharged.BlueExterior.GrayInterior.Leather.HeatedSeats

対何か

New Dealer.CarPicker(Dealer.CarPicker.Makes.Subaru
                   , Dealer.CarPicker.Models.WRX
                   , Dealer.CarPicker.Transmissions.SixSpeed
                   , Dealer.CarPicker.Engine.Options.TurboCharged
                   , Dealer.CarPicker.Exterior.Color.Blue
                   , Dealer.CarPicker.Interior.Color.Gray
                   , Dealer.CarPicker.Interior.Options.Leather
                   , Dealer.CarPicker.Interior.Seats.Heated)

連鎖の不利益
、つまり、使用したくない場合

ルーチンに渡すパラメーターが多い場合は、チェーニングを使用しません。主に行が非常に長くなるためです。また、OPで述べたように、ルーチンを他のクラスに呼び出していずれかに渡すと、混乱する可能性があります連鎖メソッド。

ルーチンが無効なデータを返すという懸念もあります。したがって、これまでは、呼び出されている同じインスタンスを返すときにのみチェーンを使用しました。指摘したように、クラス間をチェーンすると、デバッグが困難になり(どれがnullを返したのか)、クラス間の依存関係の結合を増やすことができます。

結論

人生のすべてとプログラミングのように、連鎖は良いことでも悪いことでもないので、悪いことを回避できれば、連鎖は大きなメリットになります。

私はこれらのルールに従うようにしています。

  1. クラス間をチェーンしないようにしてください
  2. チェーニング専用のルーチンを作成する
  3. 連鎖ルーチンで1つのことだけを実行する
  4. 読みやすさを向上させるときに使用します
  5. コードを簡単にするときに使用します

6

メソッドチェーンにより、Javaで高度なDSLを直接設計できます。基本的に、少なくとも次のタイプのDSLルールをモデル化できます。

1. SINGLE-WORD
2. PARAMETERISED-WORD parameter
3. WORD1 [ OPTIONAL-WORD]
4. WORD2 { WORD-CHOICE-A | WORD-CHOICE-B }
5. WORD3 [ , WORD3 ... ]

これらのルールは、これらのインターフェースを使用して実装できます

// Initial interface, entry point of the DSL
interface Start {
  End singleWord();
  End parameterisedWord(String parameter);
  Intermediate1 word1();
  Intermediate2 word2();
  Intermediate3 word3();
}

// Terminating interface, might also contain methods like execute();
interface End {}

// Intermediate DSL "step" extending the interface that is returned
// by optionalWord(), to make that method "optional"
interface Intermediate1 extends End {
  End optionalWord();
}

// Intermediate DSL "step" providing several choices (similar to Start)
interface Intermediate2 {
  End wordChoiceA();
  End wordChoiceB();
}

// Intermediate interface returning itself on word3(), in order to allow for
// repetitions. Repetitions can be ended any time because this interface
// extends End
interface Intermediate3 extends End {
  Intermediate3 word3();
}

これらの単純なルールを使用して、SQLなどの複雑なDSLをJavaに直接実装できます。これは、私が作成したライブラリjOOQによって行われます。ここの私のブログから取ったかなり複雑なSQLの例を参照してください:

create().select(
    r1.ROUTINE_NAME,
    r1.SPECIFIC_NAME,
    decode()
        .when(exists(create()
            .selectOne()
            .from(PARAMETERS)
            .where(PARAMETERS.SPECIFIC_SCHEMA.equal(r1.SPECIFIC_SCHEMA))
            .and(PARAMETERS.SPECIFIC_NAME.equal(r1.SPECIFIC_NAME))
            .and(upper(PARAMETERS.PARAMETER_MODE).notEqual("IN"))),
                val("void"))
        .otherwise(r1.DATA_TYPE).as("data_type"),
    r1.NUMERIC_PRECISION,
    r1.NUMERIC_SCALE,
    r1.TYPE_UDT_NAME,
    decode().when(
    exists(
        create().selectOne()
            .from(r2)
            .where(r2.ROUTINE_SCHEMA.equal(getSchemaName()))
            .and(r2.ROUTINE_NAME.equal(r1.ROUTINE_NAME))
            .and(r2.SPECIFIC_NAME.notEqual(r1.SPECIFIC_NAME))),
        create().select(count())
            .from(r2)
            .where(r2.ROUTINE_SCHEMA.equal(getSchemaName()))
            .and(r2.ROUTINE_NAME.equal(r1.ROUTINE_NAME))
            .and(r2.SPECIFIC_NAME.lessOrEqual(r1.SPECIFIC_NAME)).asField())
    .as("overload"))
.from(r1)
.where(r1.ROUTINE_SCHEMA.equal(getSchemaName()))
.orderBy(r1.ROUTINE_NAME.asc())
.fetch()

もう一つの良い例であるjRTF、Javaで直接RTF文書をceratingために設計された小さなDSL。例:

rtf()
  .header(
    color( 0xff, 0, 0 ).at( 0 ),
    color( 0, 0xff, 0 ).at( 1 ),
    color( 0, 0, 0xff ).at( 2 ),
    font( "Calibri" ).at( 0 ) )
  .section(
        p( font( 1, "Second paragraph" ) ),
        p( color( 1, "green" ) )
  )
).out( out );

@ user877329:はい、インターフェイスやサブタイプのポリモーフィズムなどを知っているほとんどすべてのオブジェクト指向プログラミング言語で使用できます
Lukas Eder

4

ほとんどの場合、メソッドチェーンは単に目新しさかもしれませんが、適切な場所があると思います。1つの例は、CodeIgniterのアクティブレコードの使用法にあります

$this->db->select('something')->from('table')->where('id', $id);

それは以下よりもずっときれいに見えます(そして私の意見ではより理にかなっています):

$this->db->select('something');
$this->db->from('table');
$this->db->where('id', $id);

それは本当に主観的です。誰もが自分の意見を持っています。


これはメソッドチェーンを使用たFluentインターフェイスの例であるため、UseCaseはここでは少し異なります。あなたは連鎖しているだけでなく、読みやすい内部ドメイン固有の言語を作成しています。余談ですが、CIのActiveRecordはActiveRecordではありません。
ゴードン

3

主な誤りは、これがオブジェクト指向のアプローチであり、実際には他の何よりも関数型プログラミングのアプローチであると考えることです。

私がそれを使用する主な理由は、読みやすさと、コードが変数によって氾濫するのを防ぐためです。

他の人が読みやすさを損なうと言っているとき、他の人が何を話しているのか本当にわかりません。これは、私が使用したプログラミングの中で最も簡潔でまとまりのある形式の1つです。

これも:

convertTextToVoice.LoadText( "source.txt")。ConvertToVoice( "destination.wav");

私が通常使用する方法です。これを使用してx個のパラメーターをチェーンすることは、私が通常使用する方法ではありません。メソッド呼び出しにx個のパラメーターを入れたい場合は、params構文を使用します。

public void foo(params object [] items)

タイプに基づいてオブジェクトをキャストするか、ユースケースに応じてデータ型配列またはコレクションを使用します。


1
「主な誤りは、これが一般にオブジェクト指向のアプローチであり、実際にはそれが他の何よりも関数型プログラミングのアプローチであると考えることです」と+1 。顕著な使用例は、オブジェクトに対する状態のない操作です(その状態を変更する代わりに、操作を継続する新しいオブジェクトを返します)。OPの質問とその他の回答は、実際には連鎖に不便に思われるステートフルアクションを示しています。
OmerB

はい、そうです。通常は新しいオブジェクトを作成せず、代わりに依存性注入を使用してサービスを利用可能にする以外は、状態のない操作です。そして、ステートフルユースケースは、メソッドチェーンが意図したものではないと私は思います。私が見る唯一の例外は、いくつかの設定でDIサービスを初期化し、ある種のCOMサービスのような状態を監視するある種のウォッチドッグがある場合です。ちょうど私見。
シェーンソーンダイク

2

同意します。そのため、私のライブラリに流暢なインターフェイスを実装する方法を変更しました。

前:

collection.orderBy("column").limit(10);

後:

collection = collection.orderBy("column").limit(10);

「前」の実装では、関数はオブジェクトを変更し、で終了しましたreturn this同じタイプの新しいオブジェクト返すように実装を変更しました。

この変更の私の推論

  1. 戻り値は関数とは何の関係もありません。チェーン部分をサポートするためだけに存在していました。OOPによると、これはvoid関数であるはずです。

  2. システムライブラリのメソッドチェーンは、(linqや文字列のように)そのように実装します。

    myText = myText.trim().toUpperCase();
    
  3. 元のオブジェクトはそのまま残り、APIユーザーはそれをどうするかを決定できます。次のことが可能です。

    page1 = collection.limit(10);
    page2 = collection.offset(10).limit(10);
    
  4. コピーの実装では、オブジェクトを構築するために使用することができます。

    painting = canvas.withBackground('white').withPenSize(10);
    

    どこsetBackground(color)機能は、インスタンスを変更し、何も返さない(そのはになってのように)

  5. 関数の動作はより予測可能です(ポイント1および2を参照)。

  6. 短い変数名を使用すると、モデルにAPIを強制することなく、コードを整理できます。

    var p = participant; // create a reference
    p.addSchedule(events[1]);p.addSchedule(events[2]);p.setStatus('attending');p.save()
    

結論:
私の意見では、return this実装を使用する流暢なインターフェースは間違っています。


1
しかし、呼び出しごとに新しいインスタンスを返すと、特に大きなアイテムを使用している場合は、かなりのオーバーヘッドが発生しませんか?少なくとも管理されていない言語の場合。
Apeiron

@Apeironパフォーマンスに関しては、return this管理されているかどうかにかかわらず、明らかに高速です。「自然でない」方法で流暢なAPIを取得するのは私の意見です。(理由6の追加:オーバーヘッド/追加機能のない、非流暢な代替案を示すため)
Bob Fanger

仰るとおりです。ほとんどの場合、DSLの基礎となる状態をそのままにして、すべてのメソッド呼び出しで新しいオブジェクトを返すのがより良いです。代わりに、私は興味があります。
Lukas Eder、2012年

1

ここで完全に見落としている点は、メソッドチェーンによってDRYが可能になることです。これは「with」(一部の言語では十分に実装されていない)の効果的な代役です。

A.method1().method2().method3(); // one A

A.method1();
A.method2();
A.method3(); // repeating A 3 times

これは、DRYが常に重要であるのと同じ理由で重要です。Aがエラーであることが判明し、これらの操作をBで実行する必要がある場合、更新する必要があるのは1つだけで、3つではありません。

実用的には、この場合の利点は小さいです。それでも、少しタイピングが少なく、少し堅牢(DRY)なので、それを取り上げます。


14
ソースコードで変数名を繰り返すことは、DRYの原則とは関係ありません。DRYは、「すべての知識は、システム内で単一の明確で信頼できる表現を持たなければならない」と述べており、言い換えると、知識の繰り返し(テキストの繰り返しではない)を避けています
pedromanoel

4
間違いなく、ドライに違反する繰り返しです。変数名を(不必要に)繰り返すと、他の形式の乾式実行と同じ方法ですべての悪が発生します。これにより、より多くの依存関係と作業が作成されます。上記の例では、Aの名前を変更すると、ウェットバージョンに3つの変更が必要になり、3つのうちいずれかが欠落している場合はデバッグエラーが発生します。
アンファーニー

1
すべてのメソッド呼び出しが互いに近いため、この例で指摘した問題はわかりません。また、変数の名前を1行で変更し忘れた場合、コンパイラーはエラーを返し、プログラムを実行する前に修正されます。また、変数の名前はその宣言のスコープに限定されます。この変数がグローバルでない限り、それはすでに悪いプログラミング習慣です。IMO、DRYはタイピングを減らすことではなく、物事を隔離することです。
pedromanoel

「コンパイラ」がエラーを返すか、PHPまたはJSを使用していて、この条件に達した場合、インタープリタは実行時にエラーを出力するだけです。
アンファーニー

1

読みやすさを損なうと思うので、メソッドチェーンは一般的に嫌いです。コンパクトさは読みやすさと混同されることがよくありますが、同じ用語ではありません。すべてを1つのステートメントで実行すると、コンパクトになりますが、ほとんどの場合、複数のステートメントで実行するよりも読みにくくなります(理解するのが難しくなります)。使用したメソッドの戻り値が同じであることを保証できない場合を除いて、メソッドチェーンは混乱の元になります。

1.)

participant
    .addSchedule(events[1])
    .addSchedule(events[2])
    .setStatus('attending')
    .save();

participant.addSchedule(events[1]);
participant.addSchedule(events[2]);
participant.setStatus('attending');
participant.save()

2.)

participant
    .getSchedule('monday')
        .saveTo('monnday.file');

mondaySchedule = participant.getSchedule('monday');
mondaySchedule.saveTo('monday.file');

3.)

participant
    .attend(event)
    .setNotifications('silent')
    .getSocialStream('twitter')
        .postStatus('Joining '+event.name)
        .follow(event.getSocialId('twitter'));

participant.attend(event);
participant.setNotifications('silent')
twitter = participant.getSocialStream('twitter')
twitter.postStatus('Joining '+event.name)
twitter.follow(event.getSocialId('twitter'));

ご覧のとおり、ほとんどの場合は勝ちます。なぜなら、単一のステートメントに改行を追加して読みやすくし、インデントを追加して、異なるオブジェクトについて話していることを明確にする必要があるためです。ええと、IDベースの言語を使用したい場合は、これを行う代わりにPythonを学習します。言うまでもなく、ほとんどのIDEはコードを自動フォーマットすることでインデントを削除します。

この種類のチェーンが役立つ唯一の場所は、CLIでストリームをパイプするか、SQLで複数のクエリを結合することです。どちらにも複数のステートメントの価格があります。しかし、複雑な問題を解決したい場合は、代償を払い、変数を使用して複数のステートメントでコードを記述したり、bashスクリプトやストアドプロシージャやビューを記述したりすることになります。

DRY解釈の時点で:「知識の繰り返しを避けてください(テキストの繰り返しではありません)」。そして、「タイプは少なく、テキストを繰り返さないでください。」、最初の原則は本当の意味ですが、2番目の誤解は一般的な誤解です。システム内の信頼できる表現」。2つ目は、コンパクト性であり、可読性が低下するため、このシナリオではうまくいきません。バインドされたコンテキスト間でコードをコピーすると、最初の解釈はDDDによって中断されます。そのシナリオでは、疎結合がより重要であるためです。


0

いいもの:

  1. 簡潔ですが、エレガントに1行に追加することができます。
  2. 変数の使用を回避できる場合がありますが、これが役立つ場合もあります。
  3. パフォーマンスが向上する可能性があります。

悪い人:

  1. リターンを実装し、基本的にオブジェクトのメソッドに機能を追加しますが、これらのメソッドが意図することの一部ではありません。それはあなたがすでに数バイトを節約するためにあなたがすでに持っているものを返しています。
  2. あるチェーンが別のチェーンにつながる場合、コンテキストスイッチを非表示にします。これはゲッターを使用して取得できますが、コンテキストが切り替わるタイミングは明確です。
  3. 複数行にわたるチェーニングは見苦しく、インデントではうまく機能せず、オペレーターの処理を混乱させる可能性があります(特にASIを使用する言語で)。
  4. 連鎖メソッドに役立つ他の何かを返したい場合、潜在的にそれを修正したり、より多くの問題にぶつかったりする可能性があります。
  5. 厳密に型付けされた言語であっても、通常は純粋に便宜上オフロードしないエンティティにコントロールをオフロードします。これによって引き起こされる間違いは常に検出できるとは限りません。
  6. パフォーマンスが低下する可能性があります。

一般:

良いアプローチは、状況が発生するか、特定のモジュールが特に適しているまで、一般的にチェーンを使用しないことです。

チェーニングは、特にポイント1と2で計量する場合、読みやすさを大幅に低下させる場合があります。

他のアプローチ(配列を渡すなど)の代わりに、または奇妙な方法でメソッドを混合する(parent.setSomething()。getChild()。setSomething()。getParent()。setSomething())などのように、非難されると誤用される可能性があります。


0

意見の回答

連鎖の最大の欠点は、読者が各メソッドが元のオブジェクトにどのように影響するかを理解するのが難しい場合があることです。

いくつかの質問:

  • チェーン内のメソッドは新しいオブジェクトを返しますか、それとも同じオブジェクトを変更しますか?
  • チェーン内のすべてのメソッドが同じ型を返しますか?
  • そうでない場合、チェーン内のタイプが変更されたときにどのように示されますか?
  • 最後のメソッドによって返された値を安全に破棄できますか?

デバッグは、ほとんどの言語で、チェーンを使用すると実際に困難になる可能性があります。チェーン内の各ステップが独自の行にある場合(チェーンの目的に反するものです)、特に非変異メソッドの場合、各ステップの後に返される値を検査するのは難しい場合があります。

言語とコンパイラによっては、式の解決がはるかに複雑になる可能性があるため、コンパイル時間は遅くなる可能性があります。

すべての場合と同様に、チェーンはいくつかのシナリオで便利な優れたソリューションであると思います。注意して使用し、その影響を理解し、チェーン要素の数を数に制限する必要があります。


0

型付き言語(欠落autoまたは同等のもの)では、これにより、実装者は中間結果の型を宣言する必要がなくなります。

import Participant
import Schedule

Participant participant = new Participant()
... snip...
Schedule s = participant.getSchedule(blah)
s.saveTo(filename)

より長いチェーンの場合、いくつかの異なる中間タイプを処理している可能性があるため、それぞれを宣言する必要があります。

このアプローチは、a)すべての関数呼び出しがメンバー関数呼び出しであり、b)明示的な型が必要なJavaで実際に開発されたと思います。もちろん、ここではトレードオフがあり、明示性が失われますが、状況によっては、価値があると考える人もいます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.