laertiades
laertiades

Reputation: 2012

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

Upvotes: 13

Views: 496

Answers (6)

lindon fox
lindon fox

Reputation: 3308

I think reflection might be what you are looking for. This allows you to avoid the switch statement, which is what you are asking.

Reflection (among other things) allows you to run methods with just strings. So in Java, where you would normally call a method like this:

new Foo().hello();

With Reflection, you can use a string to call the method, like this:

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

Java Constructor Reflection example.


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). The Factory pattern itself is not a means of avoiding the switch statement. 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).

Upvotes: 7

rees
rees

Reputation: 1576

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 -

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.


If you are just really into keeping it concise, you could also use a template method in the enums -

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

Upvotes: 2

laertiades
laertiades

Reputation: 2012

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. 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. 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. Your suggestions so far were very helpful and I am greatful.

Upvotes: 0

Adrian Shum
Adrian Shum

Reputation: 40036

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

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)

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

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.

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

Upvotes: 6

millimoose
millimoose

Reputation: 39950

You want to use a bunch of factory objects, stored in a Map under the string keys that you use in the switch.

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

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

Upvotes: 8

Jonathan Henson
Jonathan Henson

Reputation: 8206

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". 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.

No, this isn't reflection, people were doing this long before Java came along. This is how COM works for example.

Upvotes: 1

Related Questions