8

私は、一連のインターフェイスを実装することでユーザーがシステムを拡張できるようにするソフトウェアに取り組んでいます。

私たちが行っていることの実行可能性をテストするために、私の会社は、ユーザーが行うのとまったく同じ方法でこれらのクラスにすべてのビジネス ロジックを実装することにより、「独自のドッグ フードを食べます」。

すべてを結び付け、拡張可能なクラスで定義されたロジックを使用するいくつかのユーティリティ クラス/メソッドがあります。


ユーザー定義関数の結果をキャッシュしたい。どこでこれを行う必要がありますか?

  • それはクラス自体ですか?これは、多くのコードの重複につながる可能性があるようです。

  • これらのクラスを使用するのはユーティリティ/エンジンですか? その場合、何も知らされていないユーザーがクラス関数を直接呼び出しても、キャッシュのメリットが得られない可能性があります。


サンプルコード

public interface ILetter { string[] GetAnimalsThatStartWithMe(); }

public class A : ILetter { public string[] GetAnimalsThatStartWithMe()
                           { 
                               return new [] { "Aardvark", "Ant" }; 
                           }
                         }
public class B : ILetter { public string[] GetAnimalsThatStartWithMe()
                           { 
                               return new [] { "Baboon", "Banshee" }; 
                           } 
                         }
/* ...Left to user to define... */
public class Z : ILetter { public string[] GetAnimalsThatStartWithMe()
                           { 
                               return new [] { "Zebra" };
                           }
                         }

public static class LetterUtility
{
    public static string[] GetAnimalsThatStartWithLetter(char letter)
    {
        if(letter == 'A') return (new A()).GetAnimalsThatStartWithMe();
        if(letter == 'B') return (new B()).GetAnimalsThatStartWithMe();
        /* ... */
        if(letter == 'Z') return (new Z()).GetAnimalsThatStartWithMe();
        throw new ApplicationException("Letter " + letter + " not found");
    }
}

LetterUtilityはキャッシングを担当する必要がありますか? ILetterの個々のインスタンスはそれぞれ必要ですか? 他に完全にできることはありますか?

この例を短くしようとしているので、これらのサンプル関数はキャッシュを必要としません。しかし、(new C()).GetAnimalsThatStartWithMe()実行するたびに 10 秒かかるこのクラスを追加することを検討してください。

public class C : ILetter
{
    public string[] GetAnimalsThatStartWithMe()
    {
        Thread.Sleep(10000);
        return new [] { "Cat", "Capybara", "Clam" };
    }
}

私は、ソフトウェアを可能な限り高速にすることと、維持するコードを減らすこと (この例では結果を にキャッシュすることLetterUtility) と、まったく同じ作業を何度も行うこと (この例では、 を使用するたびに 10 秒待機すること) の間で戦っていることに気づきましたC

4

5 に答える 5

11

これらのユーザー定義可能な関数の結果をキャッシュするのに最適なレイヤーはどれですか?

答えは明らかです。目的のキャッシュ ポリシーを正しく実装できるレイヤーが適切なレイヤーです。

正しいキャッシュ ポリシーには、次の 2 つの特性が必要です。

  • 古いデータを提供してはなりません。キャッシュされているメソッドが別の結果を生成するかどうかを認識し、呼び出し元が古いデータを取得する前のある時点でキャッシュを無効にする必要があります。

  • ユーザーに代わって、キャッシュされたリソースを効率的に管理する必要があります。際限なく増大する有効期限ポリシーのないキャッシュには別の名前があります。通常、それらは「メモリ リーク」と呼ばれます。

「キャッシュは古くなっていませんか?」という質問に対する答えを知っている、システム内のレイヤーは何ですか? および「キャッシュが大きすぎますか?」これが、キャッシュを実装するレイヤーです。

于 2011-11-30T16:15:20.903 に答える
4

キャッシュのようなものは、「分野横断的な」懸念事項と見なすことができます (http://en.wikipedia.org/wiki/Cross-cutting_concern):

コンピュータ サイエンスでは、分野横断的な関心事は、他の関心事に影響を与えるプログラムの側面です。これらの問題は、多くの場合、設計と実装の両方でシステムの残りの部分からきれいに分解できず、分散 (コードの重複)、もつれ (システム間の重大な依存関係)、またはその両方が発生する可能性があります。たとえば、医療記録を処理するためのアプリケーションを作成する場合、そのような記録の簿記と索引付けは中心的な関心事ですが、記録データベースまたはユーザーデータベース、または認証システムへの変更の履歴を記録することは、横断的な関心事です。プログラムのより多くの部分に触れます。

分野横断的な関心事は、多くの場合、アスペクト指向プログラミング (http://en.wikipedia.org/wiki/Aspect-directional_programming) を介して実装できます。

コンピューティングでは、アスペクト指向プログラミング (AOP) は、分野横断的な関心事の分離を可能にすることでモジュール性を高めることを目的としたプログラミング パラダイムです。AOP は、アスペクト指向のソフトウェア開発の基礎を形成します。

.NET には、アスペクト指向プログラミングを容易にするツールが多数あります。私は、完全に透過的な実装を提供するものが最も好きです。キャッシングの例:

public class Foo
{
    [Cache(10)] // cache for 10 minutes
    public virtual void Bar() { ... }
}

あなたがする必要があるのはそれだけです...他のすべては、次のように動作を定義することによって自動的に行われます。

public class CachingBehavior
{
   public void Intercept(IInvocation invocation) { ... } 
   // this method intercepts any method invocations on methods attributed with the [Cache] attribute. 
  // In the case of caching, this method would check if some cache store contains the data, and if it does return it...else perform the normal method operation and store the result
}

これがどのように起こるかについては、2つの一般的な学校があります。

  1. ポストビルド IL ウィービング。PostSharp、Microsoft CCI、Mono Cecil などのツールは、これらの属性付きメソッドを自動的に書き換えて、動作に自動的に委任するように構成できます。

  2. ランタイム プロキシ。Castle DynamicProxy や Microsoft Unity などのツールは、ユーザーの動作に委任するプロキシ タイプ (上記の例で Bar をオーバーライドする Foo から派生したタイプ) を自動的に生成できます。

于 2011-11-30T16:39:38.533 に答える
0

以前の投稿はすべていくつかの良い点をもたらしました。私はこれをその場で書いたので、微調整が必​​要かもしれません:

interface IMemoizer<T, R>
{
   bool IsValid(T args); //Is the cache valid, or stale, etc. 
   bool TryLookup(T args, out R result);    
   void StoreResult(T args, R result); 
}

static IMemoizerExtensions
{
   Func<T, R> Memoizing<T, R>(this IMemoizer src, Func<T, R> method)
   {
      return new Func<T, R>(args =>
      {
         R result;

         if (src.TryLookup(args, result) && src.IsValid(args))
         {
            return result; 
         }
         else
         {
            result = method.Invoke(args); 
            memoizer.StoreResult(args, result); 
            return result; 
         }
      }); 
   }   
}
于 2011-11-30T16:51:25.693 に答える
0

私は C# を知りませんが、これは AOP (Aspect-Oriented Programming) を使用する場合のようです。アイデアは、実行スタックの特定のポイントで実行されるコードを「注入」できるということです。

次のようにキャッシュ コードを追加できます。

IF( InCache( object, method, method_arguments ) )
  RETURN Cache(object, method, method_arguments);
ELSE
  ExecuteMethod(); StoreResultsInCache();

次に、インターフェイス関数 (およびこれらの関数を実装するすべてのサブクラス) を呼び出すたびに、このコードを実行するように定義します。

.NET の専門家が、.NET でこれを行う方法を教えてくれませんか?

于 2011-11-30T16:14:11.173 に答える
0

一般に、キャッシングとメモ化は次の場合に意味があります。

  1. 結果を取得することは (または少なくとも可能性があります) レイテンシーが高く、キャッシュ自体に起因する費用よりも高価です。
  2. 結果には、関数への同じ入力 (つまり、引数だけでなく、結果に影響を与えるインスタンス、静的データ、およびその他のデータ) を使用した呼び出しが頻繁に行われるルックアップ パターンがあります。
  3. 問題のコードが呼び出すコード内に既存のキャッシュ メカニズムがないため、これが不要になります。
  4. 問題のコードを呼び出すコード内に、これを不要にする別のキャッシュメカニズムはありません (GetHashCode()実装が比較的高価な場合、人々はしばしば誘惑されるにもかかわらず、そのメソッド内でメモすることはほとんど意味がありません)。
  5. 古くなることはありません。キャッシュがロードされている間は古くなりそうにありません。古くなっても重要ではありません。また、古くなったことが簡単に検出できる場合もあります。

コンポーネントのすべてのユースケースがこれらすべてに一致する場合があります。彼らがそうしないところはもっとたくさんあります。たとえば、コンポーネントが結果をキャッシュするが、特定のクライアント コンポーネントによって同じ入力で 2 回呼び出されることがない場合、そのキャッシュは無駄であり、パフォーマンスに悪影響を及ぼします (無視できる場合もあれば、重大な場合もあります)。

多くの場合、クライアント コードに適したキャッシュ ポリシーを決定する方がはるかに理にかなっています。また、多くの場合、この時点で特定の用途に合わせて微調整する方が、コンポーネントよりも実世界のデータに直面する方が簡単です (実際に直面するデータは使用ごとに大きく異なる可能性があるため)。

どの程度の古さが許容できるかを知ることはさらに困難です。通常、コンポーネントは 100% の鮮度が必要であると想定する必要がありますが、クライアント コンポーネントは、ある程度の古さは問題ないことを認識できます。

一方、コンポーネントは、キャッシュに役立つ情報を簡単に取得できます。これらの場合、コンポーネントは連携して機能しますが、はるかに複雑です (例としては、RESTful Web サービスで使用される If-Modified-Since メカニズムがあります。サーバーは、クライアントがキャッシュされた情報を安全に使用できることを示すことができます)。 )。

また、構成可能なキャッシング ポリシーをコンポーネントに設定することもできます。接続プールは一種のキャッシング ポリシーです。それがどのように構成可能かを検討してください。

要約すると:

どのキャッシングが可能で便利かを判断できるコンポーネント。

ほとんどの場合、これはクライアント コードです。ただし、コンポーネントの作成者によって文書化された可能性のある遅延と古さの詳細があると、ここで役立ちます.

コンポーネントの助けを借りてクライアントコードになることはあまりありませんが、それを可能にするためにキャッシュの詳細を公開する必要があります。

また、呼び出し元のコードによって構成可能なキャッシュ ポリシーを持つコンポーネントになる場合もあります。

すべての可能なユースケースが同じキャッシングポリシーによって適切に処理されることはめったにないため、コンポーネントになることはめったにありません。1 つの重要な例外は、そのコンポーネントの同じインスタンスが複数のクライアントにサービスを提供する場合です。これは、上記に影響を与える要因がそれらの複数のクライアントに分散されるためです。

于 2011-11-30T16:22:55.347 に答える