171

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

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 の結果を参照し、その前のメソッド呼び出しは参加者を参照します。コンテキストが変化するチェーンを実際に記述するのは悪い習慣かもしれませんが (そうですか?)、それでも、似ているように見えるドット チェーンが実際に同じコンテキスト内に保持されているのか、それとも結果にのみ作用するのかを常に確認する必要があります。 .

私には、メソッド チェーンは表面的には読み取り可能なコードを生成するように見えますが、ドット表記の意味をオーバーロードすると、混乱がさらに大きくなるだけです。私は自分自身をプログラミングの第一人者とは考えていないので、間違いは私のせいだと思います。だから:私は何が欠けていますか?メソッドチェーンがどういうわけか間違っていることを理解していますか? メソッドの連鎖が特に良い場合と、特に悪い場合はありますか?

補足: この質問は、質問としてマスクされた意見の表明として読み取られる可能性があることを理解しています。しかし、そうではありません - 私は、連鎖がなぜ良い習慣であると考えられているのか、そしてそれが本来のオブジェクト指向の表記法を破ると考える上でどこが間違っているのかを心から理解したいと思っています。

4

18 に答える 18

88

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

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

メソッド チェーンのアプローチはオプションでしたが、コードの記述が容易になりました (特に IntelliSense を使用する場合)。ただし、これは 1 つの孤立したケースであり、私のコードでは一般的な方法ではないことに注意してください。

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

于 2009-07-09T14:00:05.847 に答える
84

ちょうど私の2セント。

メソッドチェーンはデバッグを難しくします: - ブレークポイントを簡潔なポイントに置くことができないので、プログラムを必要な場所で正確に一時停止できます - これらのメソッドの 1 つが例外をスローし、行番号を取得した場合、何もわかりません「チェーン」のどのメソッドが問題を引き起こしたか。

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

EDIT:コメントは、メソッドチェーンと改行が別であることを述べています。それは本当です。ただし、デバッガーによっては、ステートメントの途中にブレークポイントを配置できる場合とできない場合があります。できたとしても、中間変数を含む別の行を使用すると、柔軟性が大幅に向上し、[ウォッチ] ウィンドウで多数の値を調べることができ、デバッグ プロセスに役立ちます。

于 2010-10-28T03:56:26.200 に答える
46

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

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

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

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

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

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

于 2009-07-10T14:42:33.137 に答える
27

マーティン・ファウラーはここで良い議論をしています:

メソッドチェーン

いつ使用するか

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

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

メソッド連鎖の最大の問題は仕上げの問題です。回避策はありますが、通常、これに遭遇した場合は、ネストされた関数を使用することをお勧めします。ネストされた関数は、コンテキスト変数で混乱している場合にも適しています。

于 2009-07-09T14:09:03.637 に答える
24

私の意見では、メソッドチェーンは少し目新しいものです。確かに、それはクールに見えますが、実際の利点は見当たりません。

どうですか:

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")

編集: これに関する私の意見は、過去 10 年間で変化しました。可変オブジェクトの場合、重複を少し回避するのには役立ちますが、まだ多くのメリットはありません。しかし、今では不変性をより好むようになったので、非破壊的な更新を行う方法としてメソッド チェーンが好まれており、これを常に使用しています。

于 2009-07-09T13:52:43.530 に答える
8

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

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

于 2009-07-10T03:31:25.850 に答える
7

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

例を挙げます:

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

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

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

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

クラス間の疎結合を常に目指す必要があります。

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

于 2009-07-09T16:07:49.177 に答える
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. コードを簡単にするときに使用します
于 2011-05-20T18:59:21.363 に答える
6

メソッドの連鎖により、高度なDSLを Java で直接設計できます。基本的に、少なくとも次のタイプの 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()

もう 1 つの良い例はjRTFです。これは、RTF ドキュメントを Java で直接作成するために設計された小さな 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 );
于 2012-01-05T15:40:37.207 に答える
6

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

メソッドチェーンは、本質的に悪いものでも良いものでもありません。

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

(また、多数のメソッドを連鎖させると、何かが変更された場合に非常に脆弱になることも考慮してください)

于 2009-07-09T13:51:22.940 に答える
4

メソッドの連鎖は、ほとんどの場合、単に目新しいものかもしれませんが、それはその場にあると思います。1つの例は、 CodeIgniterのActiveRecordの使用に見られるかもしれません。

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

これは、次の場合よりもはるかにクリーンに見えます(そして、私の意見では、より理にかなっています)。

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

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

于 2009-07-10T12:58:55.407 に答える
2

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

前:

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

後:

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

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

この変更の私の理由

  1. 戻り値は関数とは何の関係もありませんでした。それは純粋に連鎖部分をサポートするためのものでした。OOPによると、これはvoid関数であるはずです。

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

    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実装を使用する流暢なインターフェースはちょうど間違っています。

于 2011-08-27T14:26:35.500 に答える
1

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

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

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

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

実際には、この場合の利点は小さいです。それでも、タイピングを少し減らして、もう少し堅牢 (DRY) にしてみます。

于 2012-04-01T03:03:47.667 に答える
1

意見のある回答

チェーン化の最大の欠点は、各メソッドが元のオブジェクトにどのように影響するか、また、元のオブジェクトにどのような影響を与えるか、およびすべてのメソッドが返す型を理解するのが難しい場合があることです。

いくつかの質問:

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

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

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

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

于 2019-05-30T22:08:57.500 に答える