18

「流暢なインターフェース」は最近かなりホットな話題です。C#3.0には、それらを作成するのに役立ついくつかの優れた機能(特に拡張メソッド)があります。

参考までに、流暢なAPIは、各メソッド呼び出しが有用なものを返すことを意味します。多くの場合、メソッドを呼び出したのと同じオブジェクトであるため、物事を連鎖させ続けることができます。Martin Fowlerは、ここでJavaの例を使用して説明しています。コンセプトは次のようなものです。

var myListOfPeople = new List<Person>();

var person = new Person();
person.SetFirstName("Douglas").SetLastName("Adams").SetAge(42).AddToList(myListOfPeople);

C#で非常に便利な流暢なインターフェイスを見てきました(1つの例は、以前に尋ねたStackOverflowの質問で見つかったパラメーターを検証するための流暢なアプローチです。それは私を驚かせました。パラメーター検証ルールを表現するための非常に読みやすい構文を提供することができました。また、例外がなければ、オブジェクトのインスタンス化を回避することができました!したがって、「通常の場合」では、オーバーヘッドはほとんどありませんでした。この1つのヒントは、短時間で膨大な量を教えてくれました。もっと多くのものを見つけたいと思います。そのように)。

それで、私はいくつかの優れた例を見て議論することによってもっと学びたいと思います。では、C#で作成または表示した優れた流暢なインターフェイスは何ですか?また、それらを非常に価値のあるものにした理由は何ですか?

ありがとう。

4

11 に答える 11

9

「流暢なインターフェース」という言葉を聞いたのはこれが初めてです。しかし、頭に浮かぶ 2 つの例は、LINQ と不変コレクションです。

LINQ の内部には一連のメソッドがあり、そのほとんどは拡張メソッドであり、少なくとも 1 つの IEnumerable を受け取り、別の IEnumerable を返します。これにより、非常に強力なメソッドチェーンが可能になります

var query = someCollection.Where(x => !x.IsBad).Select(x => x.Property1);

不変型、より具体的にはコレクションは非常によく似たパターンを持っています。不変コレクションは、通常は変更操作となる新しいコレクションを返します。そのため、コレクションを構築すると、一連の連鎖したメソッド呼び出しになることがよくあります。

var array = ImmutableCollection<int>.Empty.Add(42).Add(13).Add(12);
于 2009-03-27T04:40:03.367 に答える
8

メソッド パラメーターの検証に感謝します。流暢な API に関する新しいアイデアを得ることができました。とにかく、前提条件チェックが嫌いでした...

開発中の新製品用に拡張システムを構築しました。このシステムでは、使用可能なコマンド、ユーザー インターフェイス要素などを流暢に説明できます。これは、優れた API である StructureMap と FluentNHibernate の上で実行されます。

MenuBarController mb;
// ...
mb.Add(Resources.FileMenu, x =>
{
  x.Executes(CommandNames.File);
  x.Menu
    .AddButton(Resources.FileNewCommandImage, Resources.FileNew, Resources.FileNewTip, y => y.Executes(CommandNames.FileNew))
    .AddButton(null, Resources.FileOpen, Resources.FileOpenTip, y => 
    {
      y.Executes(CommandNames.FileOpen);
      y.Menu
        .AddButton(Resources.FileOpenFileCommandImage, Resources.OpenFromFile, Resources.OpenFromFileTop, z => z.Executes(CommandNames.FileOpenFile))
        .AddButton(Resources.FileOpenRecordCommandImage, Resources.OpenRecord, Resources.OpenRecordTip, z => z.Executes(CommandNames.FileOpenRecord));
     })
     .AddSeperator()
     .AddButton(null, Resources.FileClose, Resources.FileCloseTip, y => y.Executes(CommandNames.FileClose))
     .AddSeperator();
     // ...
});

そして、次のように使用可能なすべてのコマンドを構成できます。

Command(CommandNames.File)
  .Is<DummyCommand>()
  .AlwaysEnabled();

Command(CommandNames.FileNew)
  .Bind(Shortcut.CtrlN)
  .Is<FileNewCommand>()
  .Enable(WorkspaceStatusProviderNames.DocumentFactoryRegistered);

Command(CommandNames.FileSave)
  .Bind(Shortcut.CtrlS)
  .Enable(WorkspaceStatusProviderNames.DocumentOpen)
  .Is<FileSaveCommand>();

Command(CommandNames.FileSaveAs)
  .Bind(Shortcut.CtrlShiftS)
  .Enable(WorkspaceStatusProviderNames.DocumentOpen)
  .Is<FileSaveAsCommand>();

Command(CommandNames.FileOpen)
  .Is<FileOpenCommand>()
  .Enable(WorkspaceStatusProviderNames.DocumentFactoryRegistered);

Command(CommandNames.FileOpenFile)
  .Bind(Shortcut.CtrlO)
  .Is<FileOpenFileCommand>()
  .Enable(WorkspaceStatusProviderNames.DocumentFactoryRegistered);

Command(CommandNames.FileOpenRecord)
  .Bind(Shortcut.CtrlShiftO)
  .Is<FileOpenRecordCommand>()
  .Enable(WorkspaceStatusProviderNames.DocumentFactoryRegistered);

私たちのビューは、ワークスペースによって与えられたサービスを使用して、標準の編集メニュー コマンドのコントロールを構成します。

Workspace
  .Observe(control1)
  .Observe(control2)

ユーザーがコントロールにタブで移動すると、ワークスペースは自動的にコントロール用の適切なアダプターを取得し、元に戻す/やり直しおよびクリップボード操作を提供します。

これにより、セットアップ コードが大幅に削減され、さらに読みやすくなりました。


ビューを検証するために WinForms MVP モデル プレゼンターで使用しているライブラリ、FluentValidationについて話すのを忘れていました。本当に簡単で、本当にテスト可能で、本当に素晴らしいです!

于 2009-03-27T17:16:37.543 に答える
7

CuttingEdge.Conditionsの滑らかなインターフェイスが気に入っています。

彼らのサンプルから:

 // Check all preconditions:
 id.Requires("id")
    .IsNotNull()          // throws ArgumentNullException on failure 
    .IsInRange(1, 999)    // ArgumentOutOfRangeException on failure 
    .IsNotEqualTo(128);   // throws ArgumentException on failure 
 

同じチェックを処理する if ステートメントが 50 個ある場合よりも、はるかに読みやすく、メソッド内の前提条件 (および事後条件) をより効果的にチェックできることがわかりました。

于 2009-03-27T17:02:26.360 に答える
3

ここで指定されたものに加えて、人気のある RhinoMocks単体テストモック フレームワークは流暢な構文を使用して、モック オブジェクトの期待値を指定します。

// Expect mock.FooBar method to be called with any paramter and have it invoke some method
Expect.Call(() => mock.FooBar(null))
    .IgnoreArguments()
    .WhenCalled(someCallbackHere);

// Tell mock.Baz property to return 5:
SetupResult.For(mock.Baz).Return(5);
于 2009-03-27T17:28:05.487 に答える
2

System.Net.Mail用に少し流暢なラッパーを作成しました。これにより、電子メールコードがはるかに読みやすくなります(構文を覚えやすくなります)。

var email = Email
            .From("john@email.com")
            .To("bob@email.com", "bob")
            .Subject("hows it going bob")
            .Body("yo dawg, sup?");

//send normally
email.Send();

//send asynchronously
email.SendAsync(MailDeliveredCallback);

http://lukencode.com/2010/04/11/fluent-email-in-net/

于 2012-08-01T07:49:54.443 に答える
2

SubSonic 2.1には、クエリ API 用の適切なものがあります。

DB.Select()
  .From<User>()
  .Where(User.UserIdColumn).IsEqualTo(1)
  .ExecuteSingle<User>();

tweetsharpも流暢な API を多用しています。

var twitter = FluentTwitter.CreateRequest()
              .Configuration.CacheUntil(2.Minutes().FromNow())
              .Statuses().OnPublicTimeline().AsJson();

Fluent NHibernateは最近大流行しています。

public class CatMap : ClassMap<Cat>  
{  
  public CatMap()  
  {  
    Id(x => x.Id);  
    Map(x => x.Name)  
      .WithLengthOf(16)  
      .Not.Nullable();  
    Map(x => x.Sex);  
    References(x => x.Mate);  
    HasMany(x => x.Kittens);  
  }  
}  

Ninjectもそれらを使用していますが、すぐに例を見つけることができませんでした。

于 2009-03-27T16:43:29.353 に答える
1

WCF RESTスターターキットプレビュー2の新しいHttpClientは、優れた流暢なAPIです。サンプルについては、私のブログ投稿を参照してくださいhttp://bendewey.wordpress.com/2009/03/14/connecting-to-live-search-using-the-httpclient/

于 2009-03-27T03:27:33.713 に答える
1

NHibernate の Criteria API には、流暢なインターフェイスがあり、次のようなクールなことを実行できます。

Session.CreateCriteria(typeof(Entity))
    .Add(Restrictions.Eq("EntityId", entityId))
    .CreateAlias("Address", "Address")
    .Add(Restrictions.Le("Address.StartDate", effectiveDate))
    .Add(Restrictions.Disjunction()
        .Add(Restrictions.IsNull("Address.EndDate"))
        .Add(Restrictions.Ge("Address.EndDate", effectiveDate)))
    .UniqueResult<Entity>();
于 2009-03-27T05:18:18.610 に答える
0

@ John Sheehanが述べたように、Ninjectはこのタイプの API を使用してバインディングを指定します。ユーザーガイドのコード例を次に示します。

Bind<IWeapon>().To<Sword>();
Bind<Samurai>().ToSelf();
Bind<Shogun>().ToSelf().Using<SingletonBehavior>();
于 2009-03-27T16:58:46.853 に答える