C#でFluentを使用するタイミング


78

多くの点で、Fluentインターフェイスのアイデアは本当に好きですが、C#の最新の機能(初期化子、ラムダ、名前付きパラメーター)のすべてで、「それだけの価値はありますか?」、「これは正しいパターンですか?」つかいます?"。Fluentパターンをいつ使用するかについて、少なくとも自分の経験や意思決定マトリックスを受け入れてもらえますか?

結論:

これまでの回答からのいくつかの良い経験則:

  • 呼び出しはコンテキストパススルーの恩恵を受けるため、Fluentインターフェイスはセッターよりもアクションが多い場合に非常に役立ちます。
  • 流Fluなインターフェイスは、唯一の使用方法ではなく、APIの上のレイヤーと考える必要があります。
  • ラムダ、初期化子、名前付きパラメーターなどの最新の機能は、連携して動作し、流canなインターフェイスをさらに使いやすくします。

必要性を感じさせないモダンな機能の意味の例を次に示します。たとえば、次のような従業員を作成できる(おそらく悪い例の)Fluentインターフェイスを考えてみましょう。

Employees.CreateNew().WithFirstName("Peter")
                     .WithLastName("Gibbons")
                     .WithManager()
                          .WithFirstName("Bill")
                          .WithLastName("Lumbergh")
                          .WithTitle("Manager")
                          .WithDepartment("Y2K");

次のような初期化子で簡単に記述できます。

Employees.Add(new Employee()
              {
                  FirstName = "Peter",
                  LastName = "Gibbons",
                  Manager = new Employee()
                            {
                                 FirstName = "Bill",
                                 LastName = "Lumbergh",
                                 Title = "Manager",
                                 Department = "Y2K"
                            }
              });

この例のコンストラクターで名前付きパラメーターを使用することもできます。


1
良い質問が、私が思うに、その多くのウィキ質問
イヴォ

質問には「fluent-nhibernate」というタグが付けられています。流なインターフェイスを作成するか、流なnhibernate対XML構成を使用するかを決定しようとしていますか?
イリヤコーガン

1
Programmers.SEへの移行に投票
Matt Ellen

@Ilya Kogan、実際には流actuallyなインターフェースパターンの一般的なタグである "fluent-interface"とタグ付けされていると思います。この質問はnhibernateに関するものではなく、流interfaceなインターフェイスを作成するかどうかだけを述べたように。ありがとう。

1
この投稿は、Cでこのパターンを使用する方法を考えるきっかけとなりました。私の試みは、Code Reviewの姉妹サイトで見つけることができます。
オットー

回答:


28

流なインターフェイスを作成する(私はそれをいじってみました)には、より多くの労力がかかります。これは本質的にドメイン固有の言語の形式です。

言い換えれば、コードが書かれているよりもはるかに読まれている場合(そして、どのコードがそうでないのか?)、流なインターフェースを作成することを検討する必要があります。

流interfacesなインターフェイスはコンテキストに関するものであり、オブジェクトを構成するための単なる方法ではありません。上記のリンクでわかるように、流なAPIを使用して以下を実現しました。

  1. コンテキスト(したがって、通常、同じことで多くのアクションをシーケンスで実行する場合、コンテキストを繰り返し宣言することなくアクションをチェーンできます)。
  2. 見つけやすさ(あなたがに行くobjectA.その後、インテリセンスはあなたにヒントをたくさん与えます。上記の私の場合は、plm.Led.あなたに内蔵されたLEDを制御するためのすべてのオプションを与え、plm.Network.あなたがネットワークインタフェースで実行できるものを提供しますが。 plm.Network.X10.あなたのサブセットを提供しますX10デバイスのネットワークアクション:コンストラクターの初期化子を使用してこれを取得することはありません(異なるタイプのアクションごとにオブジェクトを作成する必要がある場合を除き、慣用的ではありません)。
  3. リフレクション(上記の例では使用されません)-渡されたLINQ式を取得して操作する機能は、特に単体テスト用に作成した一部のヘルパーAPIで非常に強力なツールです。プロパティゲッター式を渡したり、便利な式を作成したり、それらをコンパイルして実行したり、プロパティゲッターを使用してコンテキストを設定したりできます。

私が通常行うことの1つは次のとおりです。

test.Property(t => t.SomeProperty)
    .InitializedTo(string.Empty)
    .CantBeNull() // tries to set to null and Asserts ArgumentNullException
    .YaddaYadda();

流なインターフェイスがなければ、そのようなことをどのように行えるかわかりません。

編集2:次のような、非常に興味深い読みやすさの改善を行うこともできます。

test.ListProperty(t => t.MyList)
    .ShouldHave(18).Items()
    .AndThenAfter(t => testAddingItemToList(t))
    .ShouldHave(19).Items();

返信いただきありがとうございます。ただし、Fluentを使用する理由は承知していますが、上記の新しい例のようなものを使用するより具体的な理由を探しています。
アンドリューハンロン

延長返信ありがとうございます。2つの良い経験則を概説したと思います。1)コンテキストの「パススルー」の恩恵を受ける多くの呼び出しがある場合は、Fluentを使用します。2)セッターよりもコールが多い場合は、Fluentについて考えてください。
アンドリューハンロン

2
@ach、「セッターよりも多くの呼び出し」に関するこの返信には何も表示されません。「コードは書かれているよりもずっと読まれている」という彼の声明に混乱していますか?それはプロパティゲッター/セッターに関するものではなく、コードを読む人間とコードを書く人間に関するものです。通常、コードの特定の行を変更するよりもはるかに頻繁に読み取るため、人間がコードを読みやすくすることです。
ジョーホワイト

@ジョー・ホワイト、「コール」という言葉を「アクション」に言い換えるべきでしょう。しかし、そのアイデアは今でも有効です。ありがとう。
アンドリューハンロン

テストへの反省は悪です!
アドロニウス

24

スコット・ヘンゼルマンはこれについて、ジョナサン・カーターとの彼のポッドキャスト「ヘンゼルミニッツ」のエピソード260で語っています。彼らは、流れるようなインターフェースはAPIのUIに似ていると説明しています。唯一のアクセスポイントとして流れるようなインターフェイスを提供するのではなく、「通常のAPIインターフェイス」の上にある種のコードUIとして提供する必要があります。

Jonathan Carter は彼のブログで APIデザインについても少し語っています。


情報リンクに感謝し、API上のUIはそれを見る良い方法です。
アンドリューハンロン

14

流entなインターフェイスは、コードのコンテキスト内で提供する非常に強力な機能です。

あなたの目的が、一種の疑似ブラックボックスとして単純に大規模な1行のコードチェーンを作成することである場合、おそらく間違ったツリーを作成していることになります。一方、メソッド呼び出しをチェーンしてコードの読みやすさを向上させる手段を提供することにより、APIインターフェースに価値を追加するためにそれを使用している場合、多くの良い計画と努力を払う価値があると思います。

流fluentなインターフェイスを作成する際に一般的な「パターン」になりそうなものをフォローすることは避けます。 。

重要なのは、流れるような構文をドメイン固有の言語の特定の実装と考えることです。私が話していることの良い例として、StoryQを見てください。StoryQは、DSLを非常に価値のある柔軟な方法で表現する手段として流さを採用しています。


返信いただきありがとうございます。考え抜かれた対応をするのに遅すぎることはありません。
アンドリューハンロン

メソッドの接頭辞「with」は気にしません。チェーン用のオブジェクトを返さない他のメソッドとは区別されます。例えばposition.withX(5)position.getDistanceToOrigin()
LegendLength

5

最初のメモ:私は質問の1つの仮定に問題を取り上げており、それから(この投稿の最後に)特定の結論を引き出しています。これはおそらく完全で包括的な答えにはならないので、これをCWとしてマークしています。

Employees.CreateNew().WithFirstName("Peter")…

次のような初期化子で簡単に記述できます。

Employees.Add(new Employee() { FirstName = "Peter",  });

私の目には、これら2つのバージョンは異なることを意味し、実行する必要があります。

  • 流fluentでないバージョンとは異なり、流なバージョンは、新しいオブジェクトEmployeeAddコレクションに追加されるという事実を隠しEmployeesますCreate。新しいオブジェクトがd であることを示唆するだけです。

  • の意味….WithX(…)はあいまいです。特に、withオブジェクト式のキーワードを持つF#から来ている人にとっては、値がであるプロパティを除いて、派生する新しいオブジェクトobj.WithX(x)として解釈される可能性があります。一方、2番目のバージョンでは、派生オブジェクトが作成されておらず、すべてのプロパティが元のオブジェクトに指定されていることは明らかです。objobjXx

….WithManager().With
  • これに….With…はさらに別の意味があります。プロパティの初期化の「フォーカス」を別のオブジェクトに切り替えることです。流れるようなAPIには2つの異なる意味があるという事実は、With何が起こっているのかを正しく解釈することを困難にしているためです。次のように明確になります。

    (employee).WithManager(Managers.CreateNew().WithFirstName("Bill").…)
    //                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    //                     value of the `Manager` property appears inside the parentheses,
    //                     like with `WithName`, where the `Name` is also inside the parentheses 
    

結論:流 en new T { X = x }なAPI(Ts.CreateNew().WithX(x))を使用して、シンプルで十分な言語機能を「隠す」ことは明らかにできますが、

  1. 結果の流なコードの読者は、それが何をするのかをまだ理解していることに注意する必要があります。つまり、流れるようなAPIは意味が透過的で、明確でなければなりません。このようなAPIの設計は、予想よりも多くの作業になる場合があります(使いやすさと受け入れのためにテストする必要がある場合があります)。

  2. 設計は必要以上の作業になる可能性があります。この例では、流fluentなAPIは、基礎となるAPI(言語機能)に「ユーザーの快適さ」をほとんど追加しません。流fluentなAPIは、基礎となるAPI /言語機能を「使いやすくする」必要があると言えます。つまり、プログラマーの労力を大幅に節約できるはずです。同じことを書くだけの別の方法である場合、プログラマーの生活を楽にするのではなく、デザイナーの仕事をより難しくするだけなので、おそらく価値はありません(上記の結論#1を参照)。

  3. 上記の両方の点は、流れるようなAPIが既存のAPIまたは言語機能の上のレイヤーであると黙って想定しています。この仮定は別の良いガイドラインかもしれません:流APIなAPIは、唯一の方法ではなく、何かを行うための特別な方法になる可能性があります。つまり、流optなAPIを「オプトイン」の選択肢として提供することをお勧めします。


1
私の質問に時間を割いていただきありがとうございます。私が選んだ例はよく考えられていなかったことを認めます。当時、私は実際に開発中のクエリAPIに流動的なインターフェイスを使用することを考えていました。単純化しすぎました。欠点を指摘し、良い結論点をありがとう。
アンドリューハンロン

2

私は流なスタイルが好きです、それは意図を非常にはっきりと表しています。後のオブジェクト初期化子の例では、その構文を使用するにはパブリックプロパティセッターが必要であり、流、なスタイルではありません。そうは言っても、この例では、Java風のset / getスタイルのメソッドをほとんど使用しているため、パブリックセッターよりも多くは得られません。

2番目の点に私を連れて行きます、私があなたが持っている方法で流fluentなスタイルを使用するかどうかはわかりません、多くのプロパティセッターで、私はおそらく2番目のバージョンを使用するでしょう連鎖する動詞がたくさんあります。または、設定ではなく、少なくとも多くのことを行います。


お返事ありがとうございます。あなたは良い経験則を表明していると思います。多くのセッターよりも多くのコールでFluentが優れています。
アンドリューハンロン

1

流れるようなインターフェイスという用語には慣れていませんでしたが、LINQを含め、使用してきたいくつかのAPIを思い出します。

個人的には、C#の最新の機能がこのようなアプローチの有用性をどのように妨げるかはわかりません。私はむしろ彼らが手をつないで行くと言いたいです。たとえば、拡張メソッドを使用することで、このようなインターフェイスを実現するのはさらに簡単です。

おそらく、あなたが言及した最新の機能の1つを使用して、流れるようなインターフェイスをどのように置き換えることができるかという具体的な例で答えを明確にしてください。


1
返信いただきありがとうございます-私は私の質問を明確にするために基本的な例を追加しました。
アンドリューハンロン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.