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);
}

为每种鱼种建立一个工厂。这些显然不需要是匿名类,我正在使用它们来减少混乱。

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));
}
于 2012-12-17T04:05:50.963 回答
7

我认为反射可能是您正在寻找的。这使您可以避免 switch 语句,这是您所要求的。

反射(除其他外)允许您仅使用字符串运行方法。所以在Java中,你通常会调用这样的方法:

new Foo().hello();

使用反射,您可以使用字符串来调用方法,如下所示:

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

Java 构造函数反射示例


关于工厂模式(现在参考其他答案),据我了解,这只是封装 switch 语句(或您选择使用的任何方法)。工厂模式本身并不是一种避免 switch 语句的方法。工厂模式是一件好事,但不是您所要求的。(在任何情况下,您都可能希望使用工厂模式)。

于 2012-12-17T03:45:49.050 回答
6

让我们一步一步来,看看你想走多远。

首先,你可以在FishFactory中抽象出fish的创建,这样你原来做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
    }
}

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();

要添加新类型,唯一需要编辑的代码是添加一个新的枚举声明,例如

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

它可能看起来有很多代码,但它已经编写好了,它提供了一个干净的 API 来从其他代码调用,它提供了很好的类型安全性,允许 fish 实现灵活地拥有他们想要的任何构造函数或构建器,它应该是快速执行的,并且它不需要您传递任意字符串值。


如果您真的想保持简洁,您还可以在枚举中使用模板方法 -

公共枚举 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 回答