0

シングルトンのように動作するが、そうではないコードを探しています(シングルトンが悪いため:)私が探しているものはこれらの目標を満たさなければなりません:

  1. スレッドセーフ
  2. シンプル(理解して使用する、つまり数行のコード。ライブラリ呼び出しは問題ありません)
  3. 速い
  4. シングルトンではありません。テストの場合、値を上書き(およびテスト後にリセット)できる必要があります。
  5. ローカル(必要な情報はすべて1か所にある必要があります)
  6. レイジー(値が実際に必要な場合にのみ実行)。
  7. 1回実行(RHSのコードは1回だけ実行する必要があります)

コード例:

private int i = runOnce(5); // Set i to 5
// Create the connection once and cache the result
private Connection db = runOnce(createDBConnection("DB_NAME"));

public void m() {
    String greet = runOnce("World");
    System.out.println("Hello, "+greet+"!");
}

フィールドは静的ではないことに注意してください。式のRHS(右側)だけが...ある程度「静的」です。iテストでは、greet一時的に新しい値を挿入できる必要があります。

また、このコードは、この新しいコードの使用方法の概要を示していることにも注意してください。runOnce()を何かに置き換えるか、別の場所(コンストラクター、多分、またはinit()メソッドまたはゲッター)に移動してください。ただし、LOCが少ないほど良いです。

いくつかの背景情報:

私はSpringを探していません。最も一般的なケースに使用できるコードを探しています。インターフェースを実装する必要があり、必要なテストを除いて2番目の実装はありません。モックオブジェクトを渡します。また、Springは#2、#3、#5に失敗します。構成言語を学ぶ必要があります。アプリコンテキストをどこかに設定する必要があります。XMLパーサーが必要であり、ローカルではありません(情報はいたるところに分散しています)。

#5のため、グローバル構成オブジェクトまたはファクトリは法案を満たしていません。

static final#4のためにアウトです(ファイナルを変更できません)。staticクラスローダーの問題のためににおいがしますが、おそらく内部で必要になりますrunOnce()。式のLHSでそれを避けられるようにしたいと思います。

考えられる解決策の1つは、同じオブジェクトを返すデフォルト設定でehcacheを使用することです。キャッシュに入れることができるので、いつでも値を上書きすることができます。しかし、おそらくehcacheよりもコンパクトでシンプルなソリューションがあります(これもXML構成ファイルなどが必要です)。

[編集]なぜそんなに多くの人がこれに反対するのか疑問に思います。これは有効な質問であり、ユースケースはかなり一般的です(少なくとも私のコードでは)。それで、あなたが質問(またはその背後にある理由)を理解していない場合、またはあなたが答えを持っていないか、あなたが気にしない場合、なぜ反対票を投じますか?:/

[EDIT2] Springのアプリコンテキストを見ると、すべてのBeanの99%以上に単一の実装しかないことがわかります。あなたはもっと持つことができますが、実際には、あなたは単にそうではありません。したがって、インターフェイス、実装、構成を分離する代わりに、実装(最も単純な場合)、current()メソッド、およびcurrent()の結果を初期化するための1行または2行の巧妙なコードのみを含むものを探しています。 1回(初めて呼び出されたとき)、同時に結果をオーバーライドできます(可能な場合はスレッドセーフ)。これをアトミックな「if(o == null)o = new O(); return o」と考えてください。ここで、oをオーバーライドできます。たぶん、AtomicRunOnceReferenceクラスが解決策です。

今のところ、私たち全員が毎日持っているものや使用しているものは最適ではなく、私たち全員が頭を叩いて「それだけ」と言うような不可解な解決策があると感じています。数年前に春が来たときに感じたように、私たちはすべてのシングルトンの問題がどこから来たのか、そしてそれらをどのように解決するのかを理解しました。

4

4 に答える 4

6

スレッドセーフな初期化コード (imho) の最適なイディオムは、遅延内部クラスです。クラシックバージョンは

class Outer {
  class Inner {
    private final static SomeInterface SINGLETON;

    static {
      // create the SINGLETON
    }
  }

  public SomeInterface getMyObject() {
    return Inner.SINGLETON;
  }
}

それは、スレッドセーフで、遅延読み込みで、(imho) エレガントだからです。

次に、テスト容易性と置換可能性が必要です。それが何であるかを正確に知らずにアドバイスするのは難しいですが、最も明白な解決策は依存性注入を使用することです。特にSpringを使用していて、アプリケーションコンテキストがある場合は特にそうです。

そうすれば、「シングルトン」の動作はインターフェースによって表され、それらの1つを関連するクラス(またはそれを生成するファクトリ)に挿入するだけで、もちろんテスト目的で好きなものに置き換えることができます。

于 2009-01-14T11:06:03.203 に答える
3

これが私のすべての要件を満たすソリューションです。

/** Lazy initialization of a field value based on the (correct)
* double checked locking idiom by Joschua Bloch
*
* <p>See "Effective Java, Second Edition", p. 283
*/
public abstract class LazyInit<T>
{
    private volatile T field;

    /** Return the value.
    *
    *  <p>If the value is still <code>null</code>, the method will block and
    *  invoke <code>computeValue()</code>. Calls from other threads will wait
    *  until the call from the first thread will complete.
    */
    @edu.umd.cs.findbugs.annotations.SuppressWarnings("UG_SYNC_SET_UNSYNC_GET")
    public T get ()
    {
        T result = field;
        if (result == null) // First check (no locking)
        {
            synchronized (this)
            {
                result = field;
                if (result == null) // Second check (with locking)
                {
                    field = result = computeValue ();
                }
            }
        }
        return result;
    }

    protected abstract T computeValue ();

    /** Setter for tests */
    public synchronized void set (T value)
    {
        field = value;
    }

    public boolean hasValue()
    {
        return field != null;
    }
}
于 2011-03-17T16:46:39.977 に答える
1

IoC フレームワーク (Spring/Guice/...) を使用しない場合でも、IoC 手法を使用できます。そして、私の意見では、シングルトンを回避する唯一のクリーンな方法です。

于 2009-01-14T13:39:01.790 に答える
0

使用できるソリューションの 1 つは、テストでオーバーライドできる保護されたメソッドを提供することだと思います (以前にレガシー コードをテストするために使用したソリューションです)。

次のようなものです:

private SomeObject object;

protected SomeObject getObject() {
   if (object == null) {
       object = new SomeObject();
   }
   return object;
}

次に、テストクラスで次のことができます。

public void setUp() {
   MyClassUnderTest cut = new MyClassUserTest() {
      @Override
      protected SomeObject getObject() }
         return mockSomeObject;
      }
   };
}

私はこのパターンにあまり熱心ではないと言わざるを得ません。これは、実際には必要のない保護されたフィールドを公開するためですが、インジェクションがオプションではない状況から抜け出すのに役立ちます。

于 2009-01-14T11:09:53.790 に答える