4

この長い質問で申し訳ありませんが、具体的な回答がない可能性があるものを求めているため、wiki にフラグが付けられています。閉まっていればそのまま。

私の主な質問はこれです:

基本クラスで完全に定義されていない流暢なインターフェイスを作成して、流暢なインターフェイスを使用するプログラムが既存の構造内の新しい単語を追加し、ガイド インターフェイスを維持して、ドットの後にインテリセンスこの時点で実際に適用されるキーワードのみをリストします。


IoC コンテナの書き換えを 3 回繰り返しています。2 回目の繰り返しはパフォーマンスの改善でした。この 3 回目の繰り返しは、いくつかの拡張性の問題と分離の問題を解決することです。

基本的に、拡張性の問題は何もないことです。私は最近、有効期間のあるサービスを使用したいと考えていました。有効期限が切れた後、新しいコピーを解決します。たとえば、構成ファイルを 1 分ごとに読み取りますが、それ以上の頻度ではありません。これは現在の IoC ソリューションではサポートされていませんでしたが、追加する唯一の方法は、基本クラス ライブラリにアクセスして、そこにサポートを追加することでした。これは、拡張可能なクラス ライブラリの構築に失敗したことを意味します。公平を期すために、私はそれに拡張性を組み込むつもりはありませんでしたが、後でこのようなものを追加するのがどれほど苦痛になるかを十分に理解していませんでした.

私は構成用の流暢なインターフェイスを見ています。インターフェイスにも完全な拡張性を組み込みたい (またはそれを取り除きたいので、やりたくない) ため、別の方法で行う必要があります。

そういうわけで、私はあなたの意見が必要です。流暢なインターフェイスを実際に使用した経験はほとんどありませんが、それらを使用するかなりの数のコードを見てきました。そのため、すぐに使用できる明らかな利点が 1 つあります。

  • 通常、流暢なインターフェースを使用するコードは非常に読みやすい

言い換えれば、これは:

ServiceContainer.Register<ISomeService>()
    .From.ConcreteType<SomeService>()
    .For.Policy("DEBUG")
    .With.Scope.Container()
    .And.With.Parameters
        .Add<String>("connectionString", "Provider=....")
        .Add<Boolean>("optimizeSql", true);

これよりも読みやすいです:

ServiceContainer.Register(typeof(ISomeService), typeof(SomeService),
    "DEBUG", ServiceScope.Container, new Object[] { "Provider=...", true });

したがって、読みやすさは 1 つの問題です。

ただし、プログラマーのガイダンスは別のものであり、Web またはエディターで既存のコードを読んでも簡単には理解できないものです。

基本的に、これを入力すると:

ServiceContainer.Register<ISomeService>()
    .From.|
          ^-cursor here

すると、Intellisense が利用可能な解像度の種類を表示します。それを選んだら、次のように書きます。

ServiceContainer.Register<ISomeService>()
    .From.ConcreteType<SomeService>()
    .For.|

次に、「ポリシー」などの「For」キーワードの後に​​のみ利用可能なものを取得します。

しかし、これは大きな問題ですか?あなたが使用した流暢なインターフェースはこのようなものでしたか? インターフェイスを定義するための明白な対処法は、すべてのキーワードとすべてを含むクラスまたはインターフェイスを作成して、各コンマの後のインテリセンスにすべてが含まれるようにすることですが、これにより、これが合法になる可能性もあります (たとえば、コンパイル) コード:

ServiceContainer.Register<ISomeService>()
    .From.ConcreteType<SomeService>()
    .From.Delegate(() => new SomeService())
    .From.With.For.Policy("Test");

そのため、サービスを解決する方法を指定した後は、それを再度行うことができないように流暢なインターフェイスを構築したいと思います。

  • 言い換えれば、流暢なインターフェイスは非常に使いやすく、何ができるかをガイドしてくれます。

しかし、これは典型的なものですか?リゾルバーのタイプ (ConcreteType、Delegate など)、スコープのタイプ (Factory、Container、Singleton、Cache など) などのこれらのキーワードを拡張メソッドとして追加できるようにしたいので、プログラムは、基本クラスにアクセスして変更することなく、これを行う独自の方法を定義できます。つまり、すべての中間ストップにインターフェイスを提供し、実際の重要なキーワードをそのままにしておく必要があります。これらのキーワードの実装は、必要に応じて、返す中間停止インターフェイスを 1 つ選択する必要があります。

したがって、次のインターフェイスを定義する必要があるようです。

  • xyz.From.
  • xyz.From.<Resolver here>.
  • <Resolver here>.With.
  • <Resolver here>.For.

などですが、それは私には断片的に見えます。

流暢なインターフェイスの経験がある人は、戻って、引用された回答を上部近くで読んで、簡単な回答をしてもらえますか?

4

2 に答える 2

6

2つのこと:拡張メソッドとネストされたクロージャ。それらは、すべての拡張性とインテリセンスの明快さのニーズをカバーする必要があります。


興味があれば、FluentNHibernateを構築した私の経験からいくつかのヒントを紹介します。

メソッドの連鎖は最小限に抑える必要があります。それは、とりわけ、コールチェーンの行き止まりと無期限の終了につながります。ネストされたクロージャを優先します。

たとえば、行き止まり:

Database
  .ConnectionString
    .User("name")
    .Password("xxx")
  .Timeout(100) // not possible

Databaseチェーンに入ると、チェーンに戻ることはできません。これはConnectionString、のインスタンスを返すすべての接続文字列関連のメソッドでバックアップする方法がないためですConnectionString

definite-endメソッドで書き直すこともできますが、醜いです。

Database
  .ConnectionString
    .User("name")
    .Pass("xxx")
    .Done()
  .Timeout(100)

この場合、DoneDatabaseインスタンスを返し、プライマリチェーンに戻ります。繰り返しますが、醜いです。

提案されているように、ネストされたクロージャを優先します。

Database
  .ConnectionString(cs =>
    cs.User("name");
      .Pass("xxx"))
  .Timeout(100);

クロージャはかなり自己完結型であるため、これはインテリセンスの問題をほぼカバーしています。トップレベルオブジェクトには、クロージャを取得するメソッドのみが含まれ、それらのクロージャには、その操作に固有のメソッドのみが含まれます。クロージャ内で公開されている型にのみ拡張メソッドを追加できるため、ここでは拡張性も簡単です。

また、流暢なインターフェースを英語のように読ませ ないように注意する必要があります。UseThis.And.Do.That.With.This.BecauseOf.Thatチェーンは、動詞で十分な場合にのみ、インターフェースを複雑にするのに役立ちます。

Database
  .Using.Driver<DatabaseDriver>()
  .And.Using.Dialect<SQL>()
  .If.IsTrue(someBool)

対:

Database
  .Driver<DatabaseDriver>()
  .Dialect<SQL>()
  .If(someBool)

人々は動詞を探して見つけられない傾向があるため、インテリセンスでの発見可能性は低下します。FNHの例としては、メソッドがあります。このメソッドでは、メソッドが。始まるため、単語テーブルWithTableNameを検索しても見つからない傾向があります。

また、英語を母国語としない人にとっては、インターフェースの使用がより困難になります。ほとんどの非ネイティブスピーカーは彼らが探しているものの専門用語を知っていますが、余分な言葉は彼らには明確ではないかもしれません。

于 2009-12-28T17:27:51.100 に答える
0

@James Gregoryによって提供された回答に基づいて、IoC コンテナー用の新しいプロトタイプの流れるようなインターフェイスを作成しました。これが最終的な構文です。

これにより、現在の問題が修正されます。

  1. 拡張性、拡張メソッドを使用して、新しい解決タイプ、新しいスコープ タイプなどを追加できます
  2. 流暢なインターフェイスを簡単に記述でき、同じパス接尾辞につながるキーワードを複製する必要はありません
  3. 1 回目と 2 回目の反復の実装と比較して、コードがはるかに少ない

すべてのコードは私のサンドボックスでコンパイルされるため、すべて正当な構文であり、メソッドが現時点で何も実行しないことを除けば、何も偽造されていません。

私が修正しないことに決めた 1 つのことは、インターフェイスに沿って移動するときに選択を制限する流暢なインターフェイスのガイダンス部分です。そのため、次のように書くことは完全に有効です。

IoC.Register<ILogger>()
    .From(f => f.ConcreteType<TestLogger>())
    .From(f => f.ConcreteType<AnotherLogger>()); // note, two From-clauses

おそらく、これが例外をスローするか (解決オブジェクトが既に設定されている)、最後のオブジェクトが勝つかを選択する必要があります。

コメントを残してください。

コードは次のとおりです。

using System;

namespace IoC3rdIteration
{
    public class Program
    {
        static void Main()
        {
            // Concrete type
            IoC.Register<ILogger>()
                .From(f => f.ConcreteType<TestLogger>());

            // Concrete type with parameters
            IoC.Register<ILogger>()
                .From(f => f.ConcreteType<DatabaseLogger>(ct => ct
                    .Parameter<String>("connectionString", "Provider=...")
                    .Parameter<Boolean>("cacheSql", true)));

            // Policy
            IoC.Register<ILogger>()
                .From(f => f.ConcreteType<TestLogger>())
                .Policy("DEBUG");

            // Policy as default policy
            IoC.Register<ILogger>()
                .From(f => f.ConcreteType<TestLogger>())
                .Policy("RELEASE", p => p.DefaultPolicy());

            // Delegate
            IoC.Register<ILogger>()
                .From(f => f.Delegate(() => new TestLogger()));

            // Activator
            IoC.Register<ILogger>()
                .From(f => f.Activator("IoC3rdIteration.TestService"));

            // Instance
            IoC.Register<ILogger>()
                .From(f => f.Instance(new TestLogger()));

            // WCF-wrapper
            IoC.Register<ILogger>()
                .From(f => f.WCF());

            // Sinkhole service
            IoC.Register<ILogger>()
                .From(f => f.Sinkhole());

            // Factory
            IoC.Register<IServiceFactory<ILogger>>()
                .From(f => f.ConcreteType<LoggerFactory>());
            IoC.Register<ILogger>()
                .From(f => f.Factory());

            // Chaining
            IoC.Register<IDebugLogger>()
                .From(f => f.ConcreteType<DatabaseLogger>());
            IoC.Register<ILogger>()
                .From(f => f.ChainTo<IDebugLogger>());
                // now "inherits" concrete type

            // Generic service
            IoC.Register(typeof(IGenericService<>))
                .From(f => f.ConcreteType(typeof(GenericService<>)));

            // Multicast
            IoC.Register<ILogger>()
                .From(f => f.Multicast(
                    r1 => r1.From(f1 => f1.ConcreteType<TestLogger>()),
                    r2 => r2.From(f2 => f2.Delegate(() => new TestLogger())),
                    r3 => r3.From(f3 => f3.Instance(new DebugLogger()))));

            // Factory-scope
            IoC.Register<ILogger>()
                .From(f => f.ConcreteType<TestLogger>())
                .Scope(s => s.Factory());

            // Thread-scope
            IoC.Register<ILogger>()
                .From(f => f.ConcreteType<TestLogger>())
                .Scope(s => s.Thread());

            // Session-scope (ASP.NET)
            IoC.Register<ILogger>()
                .From(f => f.ConcreteType<TestLogger>())
                .Scope(s => s.Session());

            // Request-scope (ASP.NET)
            IoC.Register<ILogger>()
                .From(f => f.ConcreteType<TestLogger>())
                .Scope(s => s.Request());

            // Singleton-scope
            IoC.Register<ILogger>()
                .From(f => f.ConcreteType<TestLogger>())
                .Scope(s => s.Singleton());

            // Singleton-scope with lifetime
            IoC.Register<ILogger>()
                .From(f => f.ConcreteType<TestLogger>())
                .Scope(s => s.Singleton(si => si.LifeTime(10000)));

            // Container-scope
            IoC.Register<ILogger>()
                .From(f => f.ConcreteType<TestLogger>())
                .Scope(s => s.Container());

            // Container-scope with lifetime
            IoC.Register<ILogger>()
                .From(f => f.ConcreteType<TestLogger>())
                .Scope(s => s.Container(c => c.LifeTime(10000)));

            // Pooled-scope
            IoC.Register<ILogger>()
                .From(f => f.ConcreteType<TestLogger>())
                .Scope(s => s.Pool(p => p
                    .Minimum(1)             // always one instance in pool
                    .Typical(5)             // reduce down to 5 if over 5
                    .Maximum(10)            // exception if >10 in pool
                    .AutoCleanup()          // remove on background thread >5
                    .Timeout(10000)));      // >5 timeout before removal
        }
    }
}
于 2009-12-29T10:02:19.330 に答える