简体   繁体   English

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

[英]use variable with “new” when creating object

I am designing a virtual aquarium. 我正在设计一个虚拟水族馆。 I have a class: Fish which I inherit to create classes of different species. 我有一个类:我继承的鱼,以创建不同物种的类。 The user can select the species in a combo box and click a button to put the fish in the tank. 用户可以在组合框中选择物种,然后单击按钮将鱼放入罐中。 I use the following code to create the fish: 我使用以下代码创建鱼:

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

"stock" is a LinkedList and "s" is the String selected in the Jcombobox. “stock”是LinkedList,“s”是在Jcombobox中选择的String。 As it stands I will have to create a long switch when I add a bunch of different species. 当我添加一堆不同的物种时,我将不得不创建一个长开关。 I would like the code to look like: 我希望代码看起来像:

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

and dispense with the switch such that all I have to do is create the class and add its name to the combo box and have it work. 并且省去了开关,这样我所要做的就是创建类并将其名称添加到组合框并让它工作。 Is there a way to do so? 有办法吗? Any help is appreciated. 任何帮助表示赞赏。

You want to use a bunch of factory objects, stored in a Map under the string keys that you use in the switch . 您想要使用一堆工厂对象,这些对象存储在您在switch使用的字符串键下的Map中。

These are the classes for the various fish you should already have. 这些是您应该拥有的各种鱼类。

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) {
        // ...
    }
}

An interface for all the fish factories. 所有鱼类工厂的界面。 A fish factory represents a way to create some type of fish. 鱼类工厂代表了一种创造某种鱼类的方法。 You didn't mention what the constructor signature is so I just picked some types. 你没有提到构造函数签名是什么,所以我选择了一些类型。

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

Set up one factory for every fish type. 为每种鱼类设置一个工厂。 These obviously don't need to be anonymous classes, I'm using them to cut down on clutter. 这些显然不需要是匿名类,我用它们来减少混乱。

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

Then just pick the factory from the Map using the string you already have. 然后使用您已有的字符串从Map选择工厂。 You might want to check whether a factory for the given name exists. 您可能想要检查是否存在给定名称的工厂。

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

Now, if all your fish classes have the exact same constructor signature, you can create a single factory class that can handle all of them using reflection, and get rid of some boilerplate. 现在,如果所有的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);
        }
    }
}

Then register it for every applicable subclass. 然后为每个适用的子类注册它。

for (Class<? extends FishBase> fishClass : 
        Arrays.asList(Keegan.class,GoldenBarb.class)) {
    fishFactories.put(fishClass.getSimpleName(), 
                      new ReflectionFishFactory(fishClass));
}

I think reflection might be what you are looking for. 我认为反思可能就是你在寻找的东西。 This allows you to avoid the switch statement, which is what you are asking. 这允许您避免使用switch语句,这就是您所要求的。

Reflection (among other things) allows you to run methods with just strings. 反射(除其他外)允许您只使用字符串运行方法。 So in Java, where you would normally call a method like this: 所以在Java中,你通常会调用这样的方法:

new Foo().hello();

With Reflection, you can use a string to call the method, like this: 使用Reflection,您可以使用字符串来调用方法,如下所示:

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

Java Constructor Reflection example . Java构造函数反射示例


Regarding the Factory pattern (referring now to other answers), as I understand it, that is just encapsulating the switch statement (or whatever method you choose to use). 关于工厂模式(现在参考其他答案),据我所知,这只是封装switch语句(或您选择使用的任何方法)。 The Factory pattern itself is not a means of avoiding the switch statement. Factory模式本身不是避免switch语句的一种方法。 The Factory Pattern is a good thing, but not what you were asking. 工厂模式是一件好事,但不是你问的问题。 (You will probably want to use the factory pattern in any case). (在任何情况下,您可能都希望使用工厂模式)。

Let's go step by step to see how far you want to go. 让我们一步一步看看你想走多远。

First, you can abstract out the creation of fish in a FishFactory, so that the original place you do the switch statement can simply changed to 首先,您可以在FishFactory中抽象出鱼的创建,这样您执行switch语句的原始位置可以简单地更改为

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

Then the switch case goes to the factory: 然后开关盒进入工厂:

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

(I assume all your fish is having same interface/base class as Fish) (我假设你的所有鱼都有与Fish相同的界面/基类)

If you want to make the creation look more elegant, there are two common ways to choose from: 如果您想让创作看起来更优雅,有两种常用的方法可供选择:

Reflection Idea is simple. 反思想法很简单。 First setup a lookup table of string vs fish class (or constructor), and each createFish() is creating new instance of fish by reflection 首先设置字符串与鱼类(或构造函数)的查找表,每个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 For some reason, you may not want to use reflection (maybe due to slowness of reflection, or different fishes have very different way to create), you may look into Prototype Pattern of GoF. 原型模式由于某些原因,您可能不想使用反射(可能由于反射的缓慢,或者不同的鱼有非常不同的创建方式),您可以查看GoF的原型模式。

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

A combination of enums and factory strategies could be used for a simple, type-safe, way of creating object instances from Strings and for providing a set (or array) of Strings. 枚举和工厂策略的组合可用于从字符串创建对象实例以及提供字符串集(或数组)的简单,类型安全的方式。

Take the follwoing eample - 以下为例 -

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

This enum could then be used in the following manner - 然后可以按以下方式使用此枚举 -

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

or 要么

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

or, if you need to know the type of the created fish, you can use this call - 或者,如果您需要知道所创建的鱼的类型,您可以使用此调用 -

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

To populate the items in a menu or obtain all fish types for any other reason, you can use the names() method - 要填充菜单中的项目或因任何其他原因获取所有鱼类,您可以使用names()方法 -

String[] names = FishType.names();

To add new types, the only code that needs to be edited is to add a new enum declaration such as 要添加新类型,需要编辑的唯一代码是添加新的枚举声明,例如

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

It may seem like a lot of code, but it's already been written, it provides a clean API to call from other code, it provides pretty good type-safety, allows the fish implementations the flexibility to have whatever constructors or builders that they want, it should be fast performing, and it doesn't require you to pass around arbitrary string values. 它可能看起来像很多代码,但它已经编写好了,它提供了一个干净的API来调用其他代码,它提供了非常好的类型安全性,允许fish实现灵活地拥有他们想要的任何构造函数或构建器,它应该是快速执行的,并且它不需要您传递任意字符串值。


If you are just really into keeping it concise, you could also use a template method in the enums - 如果你真的要保持简洁,你也可以在枚举中使用模板方法 -

public enum FishType { 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);

} }

With this, you still get a lot of the same functionality such as 有了这个,你仍然可以获得许多相同的功能,例如

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

and

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

and even 乃至

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

Study the Factory Design Pattern. 研究工厂设计模式。 That is essentially what you are doing here, but will be a little bit cleaner if you use it explicitly. 这基本上就是你在这里所做的,但如果你明确地使用它会更加清洁。

It is not always just a giant switch statement. 它并不总是一个巨大的开关声明。 For instance, you may have a table of dynamically loaded assemblies and/or types, each of which have a function called "GetTypeName" and another function called "CreateInstance". 例如,您可能有一个动态加载的程序集和/或类型的表,每个程序集都有一个名为“GetTypeName”的函数和另一个名为“CreateInstance”的函数。 You would pass a string to a factory object, which would look in the table for that typename and return the result of the CreateInstance function on that factory object. 您可以将字符串传递给工厂对象,该工厂对象将在表中查找该类型名称,并在该工厂对象上返回CreateInstance函数的结果。

No, this isn't reflection, people were doing this long before Java came along. 不,这不是反思,人们在Java出现之前就已经这么做了。 This is how COM works for example. 这就是COM的工作原理。

Reflection seems to be the best solution for this issue and I am glad to have this technique in my toolbox. 反射似乎是这个问题的最佳解决方案,我很高兴在我的工具箱中使用这种技术。 Here is the code that worked: 这是有效的代码:

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) {...

I had to include the package name as part of the class string. 我必须将包名称包含在类字符串中。 I also had to make the constructors public. 我还必须公开构造函数。 I was unable to implement this solution with int arguments in the constructors but I managed to find a way around using them which was cleaner anyways. 我无法在构造函数中使用int参数实现此解决方案,但我设法找到一种方法来使用它们,无论如何都是更干净。 The only problem now is that I must update the array of Strings used in the JComboBox everytime I add a new species of Fish. 现在唯一的问题是我每次添加一种新的鱼类时都必须更新JComboBox中使用的字符串数组。 If anyone knows a way of having java generate a list of the names of all the classes in a package which inherit from a given base class that would be helpful. 如果有人知道如何让java生成一个包中所有类的名称列表,这些类继承自给定的基类,这将是有帮助的。 Your suggestions so far were very helpful and I am greatful. 到目前为止,您的建议非常有用,我很高兴。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM