私にはクラスがあります、と言いますFoo,
そしてFooのすべてのインスタンスは、同じListオブジェクトを必要とし、それを含みます、myList.
すべてのクラスインスタンスが同じリストオブジェクトを共有するので、コンストラクターが呼び出される前に、myList
を静的にし、静的関数を使用してmyList
を設定するとよいと思いました。
コンストラクターの前にセッターを呼び出す必要があるため、これが悪いのではないかと思っていました。そうしないと、プログラムがクラッシュします。別の方法は、毎回myList
を渡すことです。
特定のメソッドをbeforeの前に呼び出す必要がある場合、別のメソッドは Sequential Coupling と呼ばれ、多くの場合、アンチパターンになります。このコードジェネレーターを例にとってみましょう。
_cg.enterScope();
... // add operations inside this scope
cg.leaveScope();
_
ここで、leaveScope
は常にenterScope
の後に呼び出す必要があります。そうしないと、誤ったコードが出力されます。これは、Scope
タイプを使用するように簡単にリファクタリングできます。
_Scope(scoped_operations).generate_code(cg);
_
そのクラスのインスタンスが作成される前に静的メンバーを遅延初期化する必要があるため、ケースはより複雑です。私はこれが次のように見えると思います
_TheClass.initialize();
var instance = new TheClass(); // first use
_
最初のステップは、initialize
をスレッドセーフにすることです。次に、すでに実行されているかどうかを記録する必要があります。もしそうなら、将来の呼び出しは単に短絡します:
_static bool is_initialized = false;
static synchronized void initialize() {
if (is_initialized) return;
...; // normal initialization
is_initialized = true;
}
_
これで、コンストラクター内でこの呼び出しを実行して、他のコードが実行される前にクラスが常に初期化されるようにすることができます。
_TheClass(...) {
if (!is_initialized) initialize(); // try to avoid call to synchronized method
...;
}
_
初期化を遅らせる必要がない場合は、さまざまな単純なソリューションを使用できます。 (このコンテキストでは、遅延初期化とは、インスタンスが作成された場合にのみ初期化が実行されることを意味します)。 Listオブジェクトの作成としてcheapのようなものの場合、作成されたオブジェクトに関係なく、おそらく初期化する必要があります。言語が異なれば、メカニズムも異なります。
_// Java, C#
static List<Foo> myList = new List<Foo>();
// Java: static initializer block
static {
myList = new List<Foo>();
}
_
初期化値がコンパイル時にわからないが、むしろユーザーによって提供される場合、これは再び複雑になります。一部の言語では、クラス自体をパラメーター化できます。 TheClass = new TheMetaClass(42); instance = new TheClass()
。メタオブジェクトプロトコルがない言語では、代わりにファクトリパターンを使用できます:factory = new TheClassFactory(42); instance = factory.create()
。これは最初の順次結合の例に似ていますが、ファクトリを使用すると、型システムはインスタンスが作成される前に初期化が行われたことを保証できます。
はい、それは悪いです。
コンストラクターで常にリストを渡す代替案を検討してください。各オブジェクトに同じリストを受信させることもできます。オブジェクトのリストをファクトリに保存し、それをnewに渡します。
また、クラスは、このインスタンスを取得するコンストラクターのみを提供することでリストインスタンスが必要であることをアドバタイズするため、ユーザーは、ファクトリを使用していない場合はnullを渡すことを認識します。これは、静的関数を呼び出す必要があるよりもはるかに明白です。
C#では、静的コンストラクターでリストインスタンスを暗黙的に作成できます。
static List<int> myList = new List<int>();
public Foo()
{
myList.Add(42); // note: not thread safe!!!
}
または明示的に:
static List<int> myList;
static Foo()
{
myList = new List<int>();
}
public Foo()
{
myList.Add(42); // note: not thread safe!!!
}
これは、いくつかの理由で個別に設定するよりも優れていますが、ほとんどの場合、次のようになります。
より一般的には、コンストラクターでクラスを「すぐに使用できる」ようにし、静的コンストラクターで静的メンバーを作成することをお勧めします。これにより、初期化されていないフィールドを持つインスタンスが発生しなくなります。
特定の呼び出し順序を要求することは、 Sequential Coupling として知られるアンチパターンである可能性があり、必要な場合を除いて、それを回避します。
グローバル状態にすることで、FooFactory
をシングルトンとして効果的に実装しました(まあ、static
フィールドを使用しますが、効果は同じです)。
ファクトリパーツを明示的にして、シングルトンパーツを削除するのはどうですか(シングルトンは遠隔作用の問題のためにまったくテストできないため):
public class FooFactory {
private List myList;
public FooFactory(List myList) {
// ideally probably create an immutable copy of it
this.myList = Collections.unmodifiableList(new ArrayList(myList));
// ensure that myList has all the required characteristics
if (this.myList.isEmpty()) {
throw new IllegalArgumentException("Can't build a FooFactory with an empty myList");
}
}
public Foo newFoo() {
return new Foo(myList);
}
}
これは、パッケージプライベートのFoo
コンストラクター(誰も直接の危険なルートに行けないようにするため)とともに、適切なFoo
オブジェクトのみが作成されることを保証します。
FooFactory
を明示的に参照します。FooFactory
(不変であるため)Foo
オブジェクトを誤って作成することはできません別の可能な解決策は、 シングルトン デザインパターンを使用することです。
実際の例としては、ワーカーに実行するタスクがあり、そのタスクで共有リストを使用する必要がある場合、マネージャーは、タスクが必要なリストをすでに知っているのに、使用するリストをワーカーに指示する必要があるのはなぜですか。
myList
クラスをシングルトンにしてから、Foo
クラスのコンストラクターで、myList
オブジェクトの1つのインスタンスへのポインターを取得します。
Class myList
{
static myList * get_instance()
{
static myList m_instance;
return &m_instance;
}
};
Class Foo
{
public Foo()
{
myList * m_list = MyList::get_instance();
}
};
このソリューションは、共有リストを気にするためにオブジェクトを作成するコードを必要とせず、単にFooオブジェクトを作成するため、記載されている要件に対して最も単純です。