2011-02-09 7 views
4

NAMEという静的なStringクラス属性に基づいて、実行時にオブジェクトを作成する方法を簡単に拡張する方法を見つけようとしています。汎用ファクトリメソッドを1つ使用してインスタンスを作成する

単純なif構文を使用するこのコードをどのように改善できますか?

public class FlowerFactory { 

private final Garden g; 

public FlowerFactory(Garden g) { 
    this.g = g; 
} 

public Flower createFlower(final String name) { 
    Flower result = null; 

    if (Rose.NAME.equals(name)) { 
     result = new Rose(g); 
    } else if (Oleander.NAME.equals(name)) { 
     result = new Oleander(g); 
    } else if ... { ... } ... 

    return result; 
} 

newInstance()は、コンストラクタ引数を削除しない限り、これらのクラスで使用できません。サポートされているすべての花クラス参照のマップ(マップ)を作成し、コンストラクタ引数をプロパティ設定メソッドに移動するか、その他の簡単な解決方法はありますか?

背景情報:私の目標は、FlowerFactory.getInstance().register(this.NAME, this.class)によって、新しいFlowerクラスの何らかの「自己登録」を実装することです。これは、これまでの非常に良い回答から、イントロスペクションベースのソリューションが最も適していることを意味します。

答えて

2

あなたは、コンストラクタの引数持つにもかかわらず、リフレクションを使用することができます。クラスへのマッピング静的な名前と組み合わせる

Rose.class.getConstructor(Garden.class).newInstance(g); 

が、これはこのように実装することができます花はに移入することができ

// TODO handle unknown name 
FLOWERS.get(name).getConstructor(Garden.class).newInstance(g); 

静的イニシャライザブロック:

static { 
    Map<String, Class<? extends Flower>> map = new HashMap<String, Class<? extends Flower>>(); 
    map.put(Rose.NAME, Rose.class); 
    // add all flowers 
    FLOWERS = Collections.unmodifieableMap(map); 
} 
0

は、私はあなたのファクトリオブジェクトから状態を削除示唆し、staticファクトリメソッドでは、引数としてあなたガーデンオブジェクトを渡します:

public class FlowerFactory { 

private FlowerFactory() {} 

public static Flower createFlower(final String name, Garden g) { 
    Flower result = null; 

    if (Rose.NAME.equals(name)) { 
     result = new Rose(g); 
    } else if (Oleander.NAME.equals(name)) { 
     result = new Oleander(g); 
    } else if ... { ... } ... 

    return result; 
} 
+0

なぜあなたはそれを提案しますか? – sfussenegger

+0

@sfussenegger:メソッドを静的にしたい場合はトレードオフです – dimitrisli

+0

が間違った答えか間違った質問のどちらかです) – sfussenegger

4

一つの可能​​性は、列挙型を使用してだろう。最も単純なレベルでは、あなたが列挙値でRose.NAMEのような定数を交換し、インスタンス化する列挙値とクラス間の内部マッピングを維持することができます:

public enum Flowers { 
    ROSE(Rose.class), 
    OLEANDER(Oleander.class); 

    private final Class<? extends Flower> flowerClass; 

    Flowers(Class<? extends Flower> flowerClass) { 
     this.flowerClass = flowerClass; 
    } 

    public Flower getFlower() { 
     Flower flower = null; 
     try { 
      flower = flowerClass.newInstance(); 
     } catch (InstantiationException e) { 
      // This should not happen 
      assert false; 
     } catch (IllegalAccessException e) { 
      // This should not happen 
      assert false; 
     } 
     return flower; 
    } 
} 

花のクラスクラスにはデフォルトコンストラクタを持っていないので、Class.newInstance()は使用できません。したがって、リフレクションによってクラスをインスタンス化することは、(可能ではあるが)もう少し面倒です。代わりに、Prototypeを使用して新しい花のインスタンスを作成することもできます。

これにより、可能な花の名前と実際の花のクラスのマッピングを常に同期させておくことができます。新しい花クラスを追加するときは、新しいクラスインスタンスを作成するためのマッピングを含む新しい列挙値を作成する必要があります。ただし、enum aproachの問題は、使用するGardenインスタンスが起動時に固定されることです。 (それをgetFlower()のパラメータとして渡さない限り、一貫性を失う危険があります。つまり、特定の庭に特定の花のグループが作成されていることを確認することは困難です)。

さらに柔軟にするには、Springを使用して名前とコンクリート(bean)クラス間のマッピング全体を構成ファイルに移動することを検討してください。次に、あなたのファクトリは、Spring ApplicationContextをバックグラウンドでロードし、そこに定義されているマッピングを使用します。新しいフラワーサブクラスを導入するたびに、設定ファイルに新しい行を追加するだけで済みます。ただし、このアプローチでは、最も単純な形式で、構成時にGarden Beanインスタンスを修正する必要があります。

実行時に異なる庭園に切り替わり、庭園と花のグループの一貫性を確保したい場合は、花のクラスに名前の内部マップを使用する工場が最適です。マッピング自体は設定に再度格納できますが、実行時に別のGardenインスタンスを持つ別個のファクトリインスタンスをインスタンス化できます。

+0

マッピングはおそらく – willcodejavaforfood

+0

+1の列挙型に組み込むことができますが、内部マッピング "には、すべての列挙インスタンスによって実装される抽象メソッド' Flower create(Garden g);があります: 'ROSE {Flower create(Garden g){return new Rose(g); } ' – sfussenegger

+0

@sfussenegger、true。これは反射を取り除きます。これは良いことです。ただし、コストは各enum値の 'create()'メソッドをオーバーライドするための余分なコードです。多くの列挙型の値がある場合、これはコードを大きく膨らませる可能性があります。 –

0

また、一連のif/elsesを避けるために、文字列名をマップに格納することで、それを行うこともできます。

Map<String, Class> map; 
map.get(name).newInstance(); 

クラスを完全に制御できる場合は、文字列名からの直接の反映を使用してインスタンス化を実行できます。、

これ以外にも、依存性注入フレームワークを試すこともできます。これらのうちのいくつかは、文字列名からオブジェクトインスタンスを取り出す機能を提供します。

+0

はFlowersがGardenをコンストラクタ要素として扱うので、うまくいきません – sfussenegger

+0

常に正しいと仮定すると、それほど一般的でないサンプルは 'Class.getConstructor(Garden.class).newInstance(。 。) ' –

0

すべてのFlowerに同じコンストラクタシグネチャがある場合は、リフレクションを使用してコンストラクタでパラメータを設定できます。あなたはできる、

は明らかにこれは、依存性注入の王国になっているが、そうしても安全である場合は、あなたのコンストラクタで異なるパラメータの多くを持っている場合、多分それはあなたが

:)やっていることですGuiceがやっているのと同じように、渡すインスタンスを探すための各パラメータのタイプ。

1

enum、またはクラスへの単純なマッピングがある場合は、リフレクションを使用することができるマッピングです。

public Flower createFlower(final String name) { 
    try { 
     Class clazz = Class.forName("mypackage.flowers."+name); 
     Constructor con = clazz.getConstructor(Garden.class); 
     return (Flower) con.newInstance(g); 
    } catch (many exceptions) { 
     throw new cannot create flower exception. 
    } 
} 
2

あなたは抽象ファクトリメソッドと列挙型を使用することができます。私はいくつかの情報を持っているように、これは、しかし、あなたのケースでは最良の方法である場合

public enum FlowerType{ 
    ROSE("rose"){ 
    public Rose createFlower(Garden g){ 
     return new Rose(g); 
    } 
    }, 
    OLEANDER("oleander"){ 
    public Oleander createFlower(Garden g){ 
     return new Oleander(g); 
    } 
    }; 
    private final static Map<String, FlowerType> flowerTypes = new HashMap<String, FlowerType>(); 
    static { 
    for (FlowerType flowerType : values()){ 
     flowerTypes.put(flowerType.getName(), flowerType); 
    } 
    private final String name; 
    protected FlowerType(String name){ 
    this.name = name; 
    } 
    public String getName(){ 
    return name; 
    } 
    public abstract Flower createFlower(Garden g); 
    public static FlowerType getFlower(String name){ 
    return flowerTypes.get(name); 
    } 
} 

私が言うことはできません。

関連する問題