1

私は、いくつかの異なる種類の動物がいる動物園の例に取り組んでいます。ユーザーが「虎を追加」などのコマンドを入力すると、動物園に虎が追加されます。

私のコマンドインタープリタークラスには、次のようなコードがあります。

String animalName...
Animal newAnimal;
if (animalName.equals("tiger"))
    newAnimal = new Tiger();
else if (animalName.equals("elephant"))
    newAnimal = new Elephant();

ここでの問題は、新しい種類の動物がプログラムに追加されると、このコードも変更しなければならないことです。既存のクラスを何も変更せずに、Animal をサブクラス化するだけで新しい動物を追加したいと思います。

ユーザーがコマンドで指定した名前は、動物のクラス名と必ずしも同じではありません (たとえば、「ベンガル トラを追加」は BengalTiger オブジェクトを追加します)。

可能であれば、リフレクションの使用は避けたいと思います。


これは最終的なコードです:

private static String getClassName(String name) {
    char ch;
    int i;
    boolean upper = true;
    StringBuilder s = new StringBuilder("animals.");

    for (i=0; i<name.length(); i++) {
        ch = name.charAt(i);
        if (ch!=' ') {
            if (upper)
                s.append(Character.toUpperCase(ch));
            else
                s.append(Character.toLowerCase(ch));
            upper = false;
        } else
            upper = true;

    }
    return s.toString();
}

@Override
public Animal addAnimal(String s) {
    try {
        Animal animal = (Animal)Class.forName(getClassName(s)).newInstance();
        addAnimal(animal);
        return animal;
    } catch (InstantiationException e) {
        throw new IllegalArgumentException("There are no animals called like this");
    } catch (IllegalAccessException e) {
        throw new IllegalArgumentException("There are no animals called like this");
    } catch (ClassNotFoundException e) {
        throw new IllegalArgumentException("There are no animals called like this");
    } catch (ClassCastException e) {
        throw new IllegalArgumentException("There are no animals called like this");
    }
}
4

5 に答える 5

3

dynamic class loading: を使用する必要がありますClass.forName(name)。例:

String animal = ... // e.g. elephant
String className = "com.mycompany.animals." + animal.substring(0, 1).toUpperCase() + animal.subString(1);
Animal obj = (Animal)Class.forName(className);
于 2012-11-29T22:20:03.237 に答える
2

これを実現するには、Class.forName(X)を使用する必要があります。Class.forNameは、実際のクラスを文字列値としてロードするのに役立ちます。http://docs.oracle.com/javase/1.4.2/docs/api/java/lang/Class.html#forName(java.lang.String )を参照してください。

于 2012-11-29T22:17:42.133 に答える
1
Object animalObj = Class.forName(animalString);

ただし、正確な名前(同じ大文字)である必要があります。

于 2012-11-29T22:17:32.250 に答える
1

完全を期すために、AbstractFactory パターンに基づくかなり冗長なソリューションを次に示します。1 つではなく2 つのインターフェイス (動物型のファクトリと動物型自体)をサブクラス化する必要がありますが、リフレクションはありません!

interface Animal {
    String whoAmI();
}

class Tiger implements Animal {
    @Override
    public String whoAmI() {
        return "Tiger";
    }
}

interface AnimalFactory {
    Animal create();
}

abstract class AbstractAnimalFactory implements AnimalFactory {
    protected AbstractAnimalFactory(String animalName, FactoriesRegistry registry) {
        registry.register(animalName, this);
    }
}

class TigersFactory extends AbstractAnimalFactory {
    TigersFactory(FactoriesRegistry registry) {
        super("tiger", registry);
    }

    @Override
    public Animal create() {
        return new Tiger();
    }
}

class FactoriesRegistry {
    HashMap<String, AnimalFactory> byAnimalName = new HashMap<String, AnimalFactory>();

    Animal produce(String id) {
        AnimalFactory factory = byAnimalName.get(id);
        if (factory != null)
            return factory.create();
        else
            throw new IllegalArgumentException("Unknown animal " + id);
    }

    public void register(String animalName, AnimalFactory factory) {
        byAnimalName.put(animalName, factory);
    }
}

public class SOSample {
    public static void main(String[] args) {
        FactoriesRegistry registry = new FactoriesRegistry();
        TigersFactory tigersFactory = new TigersFactory(registry);
        System.out.println(registry.produce("tiger").whoAmI());
    }
}
于 2012-11-29T22:29:25.827 に答える
0

@Victor Sorokinが言及したように、リフレクションはこの問題の唯一の解決策ではありません。しかし、それはコーディングするのが最も単純でクリーンなものです!!!

リフレクションを使用したソリューション:

public interface Animal {
    // put your contract here
}

public class AnimalFactory {
    public static final AnimalFactory INSTANCE = new AnimalFactory();

    private Map<String, Class<? extends Animal>> animalTypeMap;

    private AnimalFactory() {};

    public Animal createAnimal(String key) {
        Animal animal = null;
        if (animalTypeMap != null) {
            Class<? extends Animal> animalType = animalTypeMap.get(key);
            if (animalType != null) {
                try {
                    animal = animalType.newInstance();
                } catch (Throwable error) {
                    throw new RuntimeException("Unable to create animal with key='" + key + "'", error);
                }
            }
        }
        return animal;
    }

    public Map<String, Class<? extends Animal>> getAnimalTypeMap() {
        if (animalTypeMap == null) {
            animalTypeMap = new HashMap<String, Class<? extends Animal>>();
        }
        return animalTypeMap;
    }

    public void setAnimalTypeMap(Map<String, Class<? extends Animal>> animalTypeMap) {
        this.animalTypeMap = animalTypeMap;
    }

    public void addAnimalType(String key, Class<? extends Animal> animalType) {
        getAnimalTypeMap().put(key, animalType);
    }

    public void removeAnimalType(String key, Class<? extends Animal> animalType) {
        if (animalTypeMap != null) {
            animalTypeMap.remove(key);
        }
    }    
}

そして、次のように使用します。

Animal myAnimal = AnimalFactory.INSTANCE.createAnimal("myAnimalKey");

このソリューションを使用すると、そのマップに必要な動物の種類を入力するだけです! 他の解決策もクリーン/良い解決策ですが、非常に冗長です...

于 2012-11-29T22:47:54.683 に答える