7

Open Closedの原則に違反しないように、これが実際にどのように行われるかを理解しようとしています。

URL を受け取り、html を文字列として返すファイルをダウンロードする 1 つの関数を持つ HttpFileDownloader というクラスがあるとします。このクラスは、関数を 1 つだけ持つ IFileDownloader インターフェイスを実装します。したがって、コード全体で IFileDownloader インターフェイスへの参照があり、IFileDownloader が解決されるたびに HttpFileDownloader のインスタンスを返す IoC コンテナーがあります。

その後、しばらく使用すると、その時点でサーバーがビジー状態になり、例外がスローされることがあることが明らかになります。これを回避するために、例外が発生した場合は 3 回自動再試行し、各再試行の間に 5 秒待機することにしました。

そこで、最大 3 ループの for ループで HttpFileDownloader を使用し、各ループ間で 5 秒間待機する 1 つの関数を持つ HttpFileDownloaderRetrier を作成します。HttpFileDownloadRetrier の「再試行」機能と「待機」機能をテストできるように、HttpFileDownloaderRetrier コンストラクターに IFileDownloader を取得させることで、HttpFileDownloader 依存関係を注入しました。

したがって、IFileDownloader のすべての解決で HttpFileDownloaderRetrier が返されるようにします。しかし、そうすると、HttpFileDownloadRetrier の IFileDownloader 依存関係は、HttpFileDownloader ではなく、それ自体のインスタンスを取得します。

したがって、IFileDownloaderNoRetry という名前の HttpFileDownloader 用の新しいインターフェイスを作成し、HttpFileDownloader を変更してそれを実装できることがわかります。しかし、それは、Open Closed に違反する HttpFileDownloader を変更していることを意味します。

または、IFileDownloaderRetrier という名前の HttpFileDownloaderRetrier 用の新しいインターフェイスを実装してから、IFileDownloader の代わりにそれを参照するように他のすべてのコードを変更することもできます。しかし、繰り返しになりますが、他のすべてのコードで Open Closed に違反しています。

それで、私はここで何が欠けていますか?既存のコードを変更せずに、既存の実装 (ダウンロード) を実装の新しいレイヤー (再試行と待機) でラップするにはどうすればよいですか?

それが役立つ場合のコードは次のとおりです。

public interface IFileDownloader
{
  string Download(string url);
}

public class HttpFileDownloader : IFileDownloader
{
  public string Download(string url)
  {
    //Cut for brevity - downloads file here returns as string
    return html;
  }
}

public class HttpFileDownloaderRetrier : IFileDownloader
{
  IFileDownloader fileDownloader;

  public HttpFileDownloaderRetrier(IFileDownloader fileDownloader)
  {
    this.fileDownloader = fileDownloader;
  }

  public string Download(string url)
  {
    Exception lastException = null;
    //try 3 shots of pulling a bad URL.  And wait 5 seconds after each failed attempt.
    for (int i = 0; i < 3; i++)
    {
      try { fileDownloader.Download(url); }
      catch (Exception ex) { lastException = ex; }
      Utilities.WaitForXSeconds(5);
    }
    throw lastException;
  }
}
4

2 に答える 2

5

サーキット ブレーカーの設計パターンを多かれ少なかれ実装しています。いつものように、DI を使用して分野横断的な問題を実装する場合、重要なのはDecoratorパターンを適用することです。

次のように CircuitBreakingFileDownloader を記述します。

public class CircuitBreakingFileDownloader : IFileDownloader
{ 
    private readonly IFileDownloader fileDownloader;

    public CircuitBreakingFileDownloader(IFileDownloader fileDownloader)
    {
        if (fileDownloader == null)
        {
            throw new ArgumentNullException("fileDownloader");
        }

        this.fileDownloader = fileDownloader;
    }

    public string Download(string url)
    {
        // Apply Circuit Breaker implementation around a call to
        this.fileDownloader.Download(url)
        // here...
    }
} 

このアプローチは、オープン/クローズの原則に従い、継承よりも合成を優先します。装飾された IFileDownloader は自身の責任に専念するのに対し、サーキット ブレーカーは単一責任の原則を満たします。

ほとんどの適切な DI コンテナーは Decorator パターンを理解するため、実際の HttpFileDownloader を含む CircuitBreakingFileDownloader を返すことで IFileDownloader の要求を解決するようにコンテナーを構成できるようになりました。

実際、このアプローチは非常に一般化できるため、汎用のサーキット ブレーカーインターセプターを調べることができます。Castle Windsor を使用した例を次に示します

于 2010-03-20T15:57:18.297 に答える
3

から直接派生するのはどうですかHttpFileDownloader:

public class HttpFileDownloader : IFileDownloader
{
    public virtual string Download(string url)
    {
        //Cut for brevity - downloads file here returns as string
        return html;
    }
}

public class HttpFileDownloaderWithRetries : HttpFileDownloader
{
    private readonly int _retries;
    private readonly int _secondsBetweenRetries;

    public HttpFileDownloaderWithRetries(int retries, int secondsBetweenRetries)
    {
        _retries = retries;
        _secondsBetweenRetries = secondsBetweenRetries;
    }

    public override string Download(string url)
    {
        Exception lastException = null;
        for (int i = 0; i < _retries; i++)
        {
            try 
            { 
                return base.Download(url); 
            }
            catch (Exception ex) 
            { 
                lastException = ex; 
            }
            Utilities.WaitForXSeconds(_secondsBetweenRetries);
        }
        throw lastException;
    }
}
于 2010-03-20T15:36:07.673 に答える