Подтвердить что ты не робот

Использовать переменную с "новым" при создании объекта

Я проектирую виртуальный аквариум. У меня есть класс: Fish, который я наследую, чтобы создавать классы разных видов. Пользователь может выбрать вид в поле со списком и нажать кнопку, чтобы поместить рыбу в резервуар. Для создания рыбы я использую следующий код:

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

и отказаться от коммутатора, чтобы все, что мне нужно сделать, это создать класс и добавить его имя в поле со списком и заставить его работать. Есть ли способ сделать это? Любая помощь приветствуется.

4b9b3361

Ответ 1

Я думаю, reflection может быть тем, что вы ищете. Это позволяет вам избегать оператора switch, о чем вы просите.

Отражение (между прочим) позволяет запускать методы только с помощью строк. Итак, на Java, где вы обычно вызываете такой метод:

new Foo().hello();

С помощью Reflection вы можете использовать строку для вызова метода, например:

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

Пример Java Constructor Reflection.


Что касается шаблона Factory (ссылаясь теперь на другие ответы), как я понимаю, это просто инкапсуляция оператора switch (или любого другого метода, который вы решите использовать). Сам шаблон Factory не является средством избежать инструкции switch. Шаблон Factory - это хорошо, но не то, что вы просили. (Вероятно, вы захотите использовать шаблон Factory в любом случае).

Ответ 2

Вы хотите использовать кучу factory объектов, хранящихся в 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) {
        // ...
    }
}

Интерфейс для всех рыбных заводов. Рыба factory представляет собой способ создания некоторого вида рыбы. Вы не указали, что такое подпись конструктора, поэтому я просто выбрал несколько типов.

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

Настройте один factory для каждого типа рыбы. Эти, очевидно, не должны быть анонимными классами, я использую их, чтобы сократить беспорядок.

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

Затем просто выберите factory из Map, используя уже существующую строку. Возможно, вы захотите проверить, существует ли factory для данного имени.

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

Теперь, если у всех ваших классов рыбы есть точная та же подпись конструктора, вы можете создать один класс factory, который может обрабатывать все из них с помощью отражения и избавиться от некоторого шаблона.

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

Ответ 3

Отпустите шаг за шагом, чтобы увидеть, как далеко вы хотите идти.

Во-первых, вы можете абстрагироваться от создания рыбы в FishFactory, так что исходное место, которое вы делаете для оператора switch, можно просто изменить на

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

Затем корпус коммутатора переходит в 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);
            //....
         }
    }
}

(Я полагаю, что у вашей рыбы одинаковый интерфейс/базовый класс, как у рыбы)

Если вы хотите, чтобы создание выглядело более изящным, есть два общих способа выбора:

Отражение Идея проста. Сначала настройте таблицу поиска строки vs класса (или конструктора), и каждый 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.

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

Ответ 4

Комбинация перечислений и factory стратегий может быть использована для простого, безопасного типа, способа создания экземпляров объекта из строк и для обеспечения набора (или массива) строк.

Возьмите следующий пример -

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 для вызова из другого кода, он обеспечивает довольно хорошую безопасность типов, позволяет реализации реализаций рыбы иметь любые конструкторы или строители, которые они хотят, он должен быть быстрым, и он не требует, чтобы вы проходили произвольные строковые значения.


Если вы просто вникнете в краткость, вы также можете использовать метод шаблона в перечислениях -

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

Ответ 5

Изучите шаблон дизайна Factory. Это, по сути, то, что вы здесь делаете, но будет немного чище, если вы используете его явно.

Это не всегда просто гигантский оператор switch. Например, у вас может быть таблица динамически загружаемых сборок и/или типов, каждая из которых имеет функцию "GetTypeName" и другую функцию "CreateInstance". Вы передадите строку в объект Factory, который будет выглядеть в таблице для этого имени и вернуть результат функции CreateInstance в этом объекте Factory.

Нет, это не отражение, люди делали это задолго до того, как появилась Java. Вот как работает COM, например.

Ответ 6

Отражение, по-видимому, является лучшим решением для этой проблемы, и я рад, что эта техника появилась в моем инструменте. Вот код, который сработал:

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 списка имен всех классов в пакете, которые наследуют от данного базового класса, что было бы полезно. Ваши предложения до сих пор были очень полезными, и я благодарен.