13

仮想水族館を設計しています。私はクラスを持っています:異なる種のクラスを作成するために継承する魚。ユーザーはコンボ ボックスで種を選択し、ボタンをクリックして魚を水槽に入れることができます。次のコードを使用して魚を作成します。

    switch(s){
        case "Keegan" :
            stock.add(new Keegan(this, x,y));
            break;
        case "GoldenBarb" :
            stock.add(new GoldenBarb(this, x,y));

「stock」は LinkedList で、「s」は Jcombobox で選択された文字列です。現状では、さまざまな種を追加するときに長いスイッチを作成する必要があります。コードを次のようにしたいと思います。

stock.add(new s(this,x,y));

クラスを作成し、その名前をコンボ ボックスに追加して機能させるだけで済むように、スイッチを省略します。そうする方法はありますか?どんな助けでも大歓迎です。

4

6 に答える 6

8

Mapで使用する文字列キーの下に格納された一連のファクトリ オブジェクトを使用したいと考えていますswitch

これらは、すでに持っているはずのさまざまな魚のクラスです。

abstract class FishBase {}

class Keegan extends FishBase {
    Keegan(Object _this, int x, int y) {
        // ...
    }
}
class GoldenBarb extends FishBase {
    GoldenBarb(Object _this, int x, int y) {
        // ...
    }
}

すべての魚工場のインターフェース。魚工場は、ある種の魚を作成する方法を表しています。コンストラクターの署名が何であるかについて言及していないので、いくつかのタイプを選択しました。

interface IFishFactory {
    FishBase newFish(Object _this, int x, int y);
}

魚の種類ごとに 1 つの工場を設定します。これらは明らかに匿名クラスである必要はありません。私はそれらを使用して混乱を減らしています。

Map<String, IFishFactory> fishFactories = new HashMap<>();

fishFactories.put("Keegan", new IFishFactory() {
    public FishBase newFish(Object _this, int x, int y) {
        return new Keegan(_this, x, y);
    }
});

fishFactories.put("GoldenBarb", new IFishFactory() {
    public FishBase newFish(Object _this, int x, int y) {
        return new GoldenBarb(_this, x, y);
    }
});

Map次に、すでに持っている文字列を使用してからファクトリを選択します。指定された名前のファクトリーが存在するかどうかを確認したい場合があります。

stock.add(fishFactories.get(s).newFish(this, x, y));

ここで、すべての fish クラスがまったく同じコンストラクター シグネチャを持っている場合、リフレクションを使用してそれらすべてを処理できる単一のファクトリ クラスを作成し、ボイラープレートを取り除くことができます。

class ReflectionFishFactory implements IFishFactory {
    Constructor<? extends FishBase> fishCtor;
    public ReflectionFishFactory(Class<? extends FishBase> fishClass) 
            throws NoSuchMethodException {

        // Find the constructor with the parameters (Object, int, int)
        fishCtor = fishClass.getConstructor(Object.class, 
                                            Integer.TYPE, 
                                            Integer.TYPE);
    }


    @Override
    public FishBase newFish(Object _this, int x, int y) {
        try {
            return fishCtor.newInstance(_this, x, y);
        } catch (InstantiationException
                | InvocationTargetException
                | IllegalAccessException e) {
            // this is terrible error handling
            throw new RuntimeException(e);
        }
    }
}

次に、該当するすべてのサブクラスに登録します。

for (Class<? extends FishBase> fishClass : 
        Arrays.asList(Keegan.class,GoldenBarb.class)) {
    fishFactories.put(fishClass.getSimpleName(), 
                      new ReflectionFishFactory(fishClass));
}
于 2012-12-17T04:05:50.963 に答える
7

リフレクションはあなたが探しているものかもしれないと思います。これにより、求めているswitchステートメントを回避できます。

リフレクション(とりわけ)を使用すると、文字列だけでメソッドを実行できます。したがって、Javaでは、通常、次のようなメソッドを呼び出します。

new Foo().hello();

Reflectionを使用すると、次のように文字列を使用してメソッドを呼び出すことができます。

Class<?> clazz = Class.forName("Foo");
clazz.getMethod("hello").invoke(clazz.newInstance());

Javaコンストラクタリフレクションの例


ファクトリパターン(他の回答を参照)に関しては、私が理解しているように、それは単にswitchステートメント(または使用することを選択した方法)をカプセル化することです。ファクトリパターン自体は、switchステートメントを回避する手段ではありません。ファクトリパターンは良いことですが、あなたが求めていたものではありません。(いずれの場合も、ファクトリパターンを使用することをお勧めします)。

于 2012-12-17T03:45:49.050 に答える
6

一歩一歩進んで、どこまで行きたいか見てみましょう。

まず、FishFactory での魚の作成を抽象化して、switch ステートメントを実行する元の場所を単純に次のように変更できます。

stock.add(fishFactory.createFish(s, x, y));

その後、スイッチ ケースは工場に送られます。

public class SimpleFishFactory {
    @Override
    public Fish createFish(String fishType, int x, int y) {
        switch(s){
            case "Keegan" :
                return new Keegan(this, x,y);
                break;
            case "GoldenBarb" :
                return new GoldenBarb(this, x,y);
            //....
         }
    }
}

(すべての魚が魚と同じインターフェース/基本クラスを持っていると仮定します)

作品をよりエレガントに見せたい場合は、次の 2 つの一般的な方法から選択できます。

リフレクション アイデアはシンプルです。最初に文字列と魚のクラス (またはコンストラクター) のルックアップ テーブルをセットアップし、それぞれcreateFish()がリフレクションによって魚の新しいインスタンスを作成します。

public class ReflectionFishFactory {

    private Map<String, Class<? extends Fish>> fishClasses = new HashMap<...>();

    public ReflectionFishFactory() {
        //set up fishClasses with name vs corresponding classes.
        // you may read it from file, or hard coded or whatever

        fishClasses.put("Keegan", Keegan.class);
        fishClasses.put("GoldenBarb", GoldenBarb.class);
    }


    @Override
    public Fish createFish(String fishType, int x, int y) {
        Class<?> fishClass = fishClasses.get(fishType);
        // use reflection to create new instance of fish by 
        // by using fishClass
    }
}

Prototype Pattern なんらかの理由で、反射を使用したくない場合があります (反射が遅いため、または魚によって作成方法が非常に異なるため)、GoF の Prototype Pattern を調べることができます。

public class PrototypeFishFactory {

    private Map<String, Fish> fishes = new HashMap<...>();

    public ReflectionFishFactory() {
        //set up fishClasses with name vs corresponding classes.
        // you may read it from file, or hard coded or whatever

        fishClasses.put("Keegan", new Keegan(....) );
        fishClasses.put("GoldenBarb", new GoldenBarb(....) );
    }


    @Override
    public Fish createFish(String fishType, int x, int y) {
        return fishes.get(fishType).cloneNewInstance(x, y);
    }
}
于 2012-12-17T04:21:32.700 に答える
2

列挙型とファクトリ戦略の組み合わせを使用して、文字列からオブジェクト インスタンスを作成し、文字列のセット (または配列) を提供する、単純でタイプ セーフな方法を使用できます。

次の例を見てください -

import java.util.HashMap;
import java.util.Map;

public enum FishType {

    BLUE_FISH(BlueFish.class, new FactoryStrategy<BlueFish>(){
        public BlueFish createFish(int x, int y) {
            return new BlueFish(x, y);
        }}),

    RED_FISH(RedFish.class, new FactoryStrategy<RedFish>(){
        public RedFish createFish(int x, int y) {
            //an example of the increased flexibility of the factory pattern - different types can have different constructors, etc.
            RedFish fish = new RedFish();
            fish.setX(x);
            fish.setY(y);
            fish.init();
            return fish;
        }});

    private static final Map<Class<? extends Fish>, FactoryStrategy> FACTORY_STRATEGY_MAP = new HashMap<Class<? extends Fish>, FactoryStrategy>();
    private static final String[] NAMES;

    private FactoryStrategy factoryStrategy;
    private Class<? extends Fish> fishClass;

    static {
        FishType[] types = FishType.values();
        int numberOfTypes = types.length;
        NAMES = new String[numberOfTypes];
        for (int i = 0; i < numberOfTypes; i++) {
            FishType type = types[i];
            FACTORY_STRATEGY_MAP.put(type.fishClass, type.factoryStrategy);
            NAMES[i] = type.name();
        }
    }

    <F extends Fish> FishType(Class<F> fishClass, FactoryStrategy<F> factoryStrategy) {
        this.fishClass = fishClass;
        this.factoryStrategy = factoryStrategy;
    }

    public Fish create(int x, int y) {
        return factoryStrategy.createFish(x, y);
    }

    public Class<? extends Fish> getFishClass() {
        return fishClass;
    }

    public interface FactoryStrategy<F extends Fish> {
        F createFish(int x, int y);
    }

    @SuppressWarnings("unchecked")
    public static <F extends Fish> FactoryStrategy<F> getFactory(Class<F> fishClass) {
        return FACTORY_STRATEGY_MAP.get(fishClass);
    }

    public static String[] names() {
        return NAMES;
    }
}

この列挙型は、次の方法で使用できます -

Fish fish = FishType.valueOf("BLUE_FISH").create(0, 0);

また

Fish fish = FishType.RED_FISH.create(0, 0);

または、作成された魚の種類を知る必要がある場合は、この呼び出しを使用できます -

BlueFish fish = FishType.getFactory(BlueFish.class).createFish(0, 0);

メニューにアイテムを追加したり、その他の理由ですべての魚の種類を取得したりするには、names() メソッドを使用できます -

String[] names = FishType.names();

新しい型を追加するために編集する必要がある唯一のコードは、次のような新しい enum 宣言を追加することです。

GREEN_FISH(GreenFish.class, new FactoryStrategy<GreenFish>(){
        public GreenFish createFish(int x, int y) {
            return new GreenFish(x, y);
        }}),

たくさんのコードのように見えるかもしれませんが、すでに書かれており、他のコードから呼び出すためのクリーンな API を提供し、かなり優れた型安全性を提供し、魚の実装が必要なコンストラクターまたはビルダーを柔軟に持つことができます。高速に実行する必要があり、任意の文字列値を渡す必要はありません。


本当に簡潔にしたい場合は、列挙型でテンプレートメソッドを使用することもできます-

public enum FishType {

BLUE_FISH(){
    public BlueFish create(int x, int y) {
        return new BlueFish(x, y);
    }
},

RED_FISH(){
    public RedFish create(int x, int y) {
        return new RedFish();
    }
};

public abstract <F extends Fish> F create(int x, int y);

}

これにより、次のような多くの同じ機能を引き続き利用できます。

Fish fish = FishType.valueOf("BLUE_FISH").create(0, 0);

Fish fish = FishType.RED_FISH.create(0, 0);

そしてさえ

RedFish fish = FishType.RED_FISH.create(0, 0);
于 2012-12-17T04:11:17.907 に答える
1

ファクトリデザインパターンを研究します。これは基本的にここで行っていることですが、明示的に使用すると少しクリーンになります。

それは必ずしも単なる巨大なswitchステートメントではありません。たとえば、動的にロードされるアセンブリやタイプのテーブルがあり、それぞれに「GetTypeName」という関数と「CreateInstance」という別の関数があります。文字列をファクトリオブジェクトに渡します。ファクトリオブジェクトはテーブルでそのタイプ名を探し、そのファクトリオブジェクトに対するCreateInstance関数の結果を返します。

いいえ、これは反映ではありません。Javaが登場するずっと前から、人々はこれを行っていました。これは、たとえばCOMがどのように機能するかです。

于 2012-12-17T03:43:23.303 に答える
0

リフレクションは、この問題に対する最良の解決策のようであり、この手法をツールボックスに追加できることをうれしく思います。動作したコードは次のとおりです。

public void addFish(String s, int qt){
    try{
        Class<?> theClass = Class.forName("ftank." + s);
        Class[] ctorArgs = {ftank.FishTank.class};
        Constructor ctor = theClass.getDeclaredConstructor(ctorArgs);
        for(int i=0;i<qt;i++){stock.add((Fish)ctor.newInstance(this));}
    } catch (ClassNotFoundException e) {...

クラス文字列の一部としてパッケージ名を含める必要がありました。また、コンストラクターを公開する必要がありました。コンストラクターで int 引数を使用してこのソリューションを実装することはできませんでしたが、とにかくクリーンなそれらを使用する方法を見つけることができました。ここでの唯一の問題は、新しい種の魚を追加するたびに、JComboBox で使用される文字列の配列を更新しなければならないことです。特定の基本クラスから継承するパッケージ内のすべてのクラスの名前のリストをJavaに生成させる方法を誰かが知っていると便利です。これまでのあなたの提案は非常に役に立ち、私は素晴らしいです。

于 2012-12-17T19:16:41.390 に答える