在创建对象时使用带有“new”的变量

时间:2012-12-17 03:39:14

标签: java

我正在设计一个虚拟水族馆。我有一个类:我继承的鱼,以创建不同物种的类。用户可以在组合框中选择物种,然后单击按钮将鱼放入罐中。我使用以下代码创建鱼:

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

并省去了开关,这样我所要做的就是创建类并将其名称添加到组合框中并让它工作。有办法吗?任何帮助表示赞赏。

6 个答案:

答案 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生成一个包中所有类的名称列表,这些类继承自给定的基类,这将是有帮助的。到目前为止,您的建议非常有用,我很高兴。