我正在设计一个虚拟水族馆。我有一个类:我继承的鱼,以创建不同物种的类。用户可以在组合框中选择物种,然后单击按钮将鱼放入罐中。我使用以下代码创建鱼:
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));
并省去了开关,这样我所要做的就是创建类并将其名称添加到组合框中并让它工作。有办法吗?任何帮助表示赞赏。
答案 0 :(得分: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);
}
为每种鱼类设置一个工厂。这些显然不需要是匿名课程,我用它们来减少混乱。
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));
现在,如果所有鱼类都具有完全相同的构造函数签名,则可以创建一个可以使用反射处理所有这些类的工厂类,并删除一些样板。
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));
}
答案 1 :(得分:7)
我认为reflection可能就是你要找的东西。这允许您避免使用switch语句,这就是您所要求的。
反射(除其他外)允许您只使用字符串运行方法。所以在Java中,你通常会调用这样的方法:
new Foo().hello();
使用Reflection,您可以使用字符串来调用方法,如下所示:
Class<?> clazz = Class.forName("Foo");
clazz.getMethod("hello").invoke(clazz.newInstance());
Java Constructor Reflection example
关于工厂模式(现在参考其他答案),据我所知,这只是封装switch语句(或您选择使用的任何方法)。 Factory模式本身不是避免switch语句的一种方法。工厂模式是一件好事,但不是你问的问题。 (在任何情况下,您可能都希望使用工厂模式。)
答案 2 :(得分: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);
//....
}
}
}
(我假设你的所有鱼都有与Fish相同的界面/基类)
如果您想让创作看起来更优雅,有两种常用的选择方式:
<强>反射强>
想法很简单。首先设置一个字符串与鱼类(或构造函数)的查找表,每个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
}
}
原型模式 出于某种原因,你可能不想使用反射(可能由于反射的缓慢,或者不同的鱼有非常不同的创建方式),你可以看看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);
}
}
答案 3 :(得分:2)
枚举和工厂策略的组合可用于从Strings创建对象实例以及提供字符串集(或数组)的简单,类型安全的方式。
采取以下示例 -
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();
要添加新类型,需要编辑的唯一代码是添加新的枚举声明,例如
GREEN_FISH(GreenFish.class, new FactoryStrategy<GreenFish>(){
public GreenFish createFish(int x, int y) {
return new GreenFish(x, y);
}}),
它可能看起来像很多代码,但它已经编写好了,它提供了一个干净的API来调用其他代码,它提供了非常好的类型安全性,允许fish实现灵活地拥有任何构造函数或构建器他们想要的是,它应该是快速执行的,并且它不需要你传递任意字符串值。
如果你真的要保持简洁,你也可以在枚举中使用模板方法 -
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);
答案 4 :(得分:1)
研究工厂设计模式。这基本上就是你在这里所做的,但如果你明确地使用它会更加清洁。
并不总是只是一个巨大的开关声明。例如,您可能有一个动态加载的程序集和/或类型的表,每个程序集都有一个名为“GetTypeName”的函数和另一个名为“CreateInstance”的函数。您可以将字符串传递给工厂对象,该工厂对象将在表中查找该类型名称,并在该工厂对象上返回CreateInstance函数的结果。
不,这不是反思,人们在Java出现之前就已经这么做了。这就是COM的工作原理。
答案 5 :(得分: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生成一个包中所有类的名称列表,这些类继承自给定的基类,这将是有帮助的。到目前为止,您的建议非常有用,我很高兴。