13

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.

Mike Samuel
  • 118,113
  • 30
  • 216
  • 245
laertiades
  • 1,992
  • 2
  • 19
  • 26
  • On my thinking, there might be not. However, I want to see other Java Devs perspective through this. – Michael 'Maik' Ardan Dec 17 '12 at 03:42
  • 1
    Perhaps you want to look into reflection? http://en.wikipedia.org/wiki/Reflection_(computer_programming)#Java – lindon fox Dec 17 '12 at 03:42
  • 1
    Take a look at http://en.wikipedia.org/wiki/Factory_method_pattern – Thihara Dec 17 '12 at 03:42
  • On a tangent, this is one thing that Javascript gets VERY RIGHT. You can just add your ctor function to a factory object and call it like any other function. 2 LOC as opposed to many many more in most other languages. – Grokys Dec 17 '12 at 14:45
  • 1
    "I had a problem, so I fired up Java. Now, I have a ProblemFactory." – Grokys Dec 19 '12 at 08:28

6 Answers6

8

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));
}
millimoose
  • 39,073
  • 9
  • 82
  • 134
  • Excellent answer, and definitely the option that I would choose. +1 – Jonathan Henson Dec 17 '12 at 04:08
  • `fishFactories.get(s).newFish(this, x, y)` – Yanick Rochon Dec 17 '12 at 04:08
  • I also added the reflection approach, which can easily be combined with the factories to let you whitelist which fish classes can be constructed using reflection, and which get custom factories. – millimoose Dec 17 '12 at 04:21
  • I would like this, except that it seems to require quite a bit more setup than pure reflection would (for very little if any gain). For C++ it might be useful... but what's the benefit in languages with reflection? – cHao Dec 17 '12 at 04:22
  • @cHao The benefit is that you can easily implement a factory using reflection, without having to use it for your whole hierarchy. From a design standpoint, you also decouple the OP's chunk of code from the details of how fish objects are created. If you need none of that you can just do the reflection stuff, but then you have to handle three exceptions which you don't really expect to occur except when they do and then you get weird reflection crap in your stack trace. – millimoose Dec 17 '12 at 04:23
  • @millimoose: I get the benefits of reflection. I'm saying, this answer handwaves away one big question: "what's the point of all this factory stuff when pure reflection would get you the same thing with less effort?". Perhaps you should have an example where, i dunno, one of the fish is constructed differently or something. – cHao Dec 17 '12 at 04:27
  • @cHao I meant to answer the OP's question, not deliver a lecture on design patterns. Your question only seems big because you seem to be preoccupied with the notion of the two techniques being mutually exclusive. That's a false premise, they address different concerns in the answer. The factory maps a method of creating an object to a string key. Reflection gets rid of boilerplate factory implementations. – millimoose Dec 17 '12 at 04:29
  • @millimoose: I'm not saying that they're mutually exclusive. Hell, i'd argue that reflection basically *is* a naked factory of sorts, considering you can get a constructor via a class name and use it to create an object. I'm saying that in the OP's case, it seems like reflection actually obviates the need for explicit factories and registration and all that. And as this answer stands, it ignores the question of why that's not the case; absent that explanation, all this factory stuff seems like pattern masturbation. Again, what's the point? Where is the gain? That's what this answer lacks. – cHao Dec 17 '12 at 05:16
  • 1
    Agree with the other comments. You've taken a basic switch statement here and exploded it into I'd guess 10x as much code, for no real purpose. – Grokys Dec 17 '12 at 05:29
  • 1
    @millimoose: (By the way: you can catch `ReflectiveOperationException`, which is the superclass of all three of those exception types. It'd also catch `ClassNotFoundException`, `NoSuchMethodException`, and `NoSuchFieldException`, but if those happen at that point and the fish aren't themselves doing crazy reflection stuff, Java's about to catch fire anyway, so.) – cHao Dec 17 '12 at 06:52
  • @Groky My purpose was to be constructive and show code – which I did for reflection, as well as the factory pattern. (As opposed to the other answers and comments that consist of wikipedia links and handwaves.) I guess I should've spend my time shitting over the other answers over pedantic aspects of design ideology instead. Yes, the factory pattern might or might not be appropriate; it's a *subjective* call based on whether you prefer KISS over the SRP, and on exactly how complex the code you're replacing is. I'm not going to read too much into a SO code sample and let the OP make that call. – millimoose Dec 17 '12 at 14:10
  • @millimoose: yes, point taken. I just wanted to chime in that the factory pattern may not be appropriate here. It's important to be aware of patterns, but it's also important to know when they are appropriate. No slight intended on the quality of your answer/code! – Grokys Dec 17 '12 at 14:36
7

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

lindon fox
  • 3,298
  • 3
  • 33
  • 59
  • 5
    Reflection is slooooooowwwww. It might not matter if this app is not very big, but IMHO, reflection is just a poor substitute for a well designed architecture. The only thing I grant to reflection is that it is great for developing plugin environments. – Jonathan Henson Dec 17 '12 at 03:46
  • 3
    Reflection is fleeeeeexxxxiiiiiibbbbllllleeee. :P Dynamic class selection isn't going to be as easily implemented without it, in a statically typed language like Java. – cHao Dec 17 '12 at 03:48
  • @cHao, it is flexible, and slow is sometimes acceptable, when you are developing ojbects that it doesn't matter how quickly they are created and mutated. However, the moment you need to do something on the scale of say, gstreamer, or COM, it will fail you every time. – Jonathan Henson Dec 17 '12 at 03:50
  • 5
    Also, it doesn't matter one bit how slow using reflection for this is if it's is only going to be performed rarely. And "once in response to a user input event" counts as "rarely". – millimoose Dec 17 '12 at 03:57
6

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);
    }
}
Adrian Shum
  • 38,812
  • 10
  • 83
  • 131
2

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);
rees
  • 1,566
  • 1
  • 12
  • 19
  • While this looks nice, this won't work. Because the `BlueFish`'s `static` initializer is executed once only when a first instance of the class is created, thus making this pattern (although promising) not workable. – Yanick Rochon Dec 17 '12 at 05:20
  • @YanickRochon you are not really correct. The static block is executed only once, but that execution is not tied to an instance. Further, in the above design it only needs to execute once. Once the strategy is added to the map, it can be reused any number of times. The code works, try it out if you doubt me. Thanks for checking it out though! – rees Dec 17 '12 at 12:55
  • 1
    @rees: Either way, the static block is only run once the class is loaded...which only happens once you refer to it. In order to make it work, you'd pretty much need to create an object or call a static function on each fish subclass for the hell of it. And unless you hard-code what they all are, how's that going to work? – cHao Dec 17 '12 at 14:18
  • @rees, if you try this example, you'll notice that the factory has nothing registered to it, unless you explicitly create an instance, or call a method of each `Fish` implementation. Just like [cHao](http://stackoverflow.com/questions/13907977/use-variable-with-new-when-creating-object/13908169#comment19180525_13908169) said. – Yanick Rochon Dec 17 '12 at 14:24
  • @rees. Thank you. I implemented your example and it is an improvement. I had to make some changes: return new BlueFish();-> – laertiades Dec 17 '12 at 15:11
  • @rees. Thank you. I implemented your example and it is an improvement. I had to make some changes: return new BlueFish(); -> return new BlueFish(x,y); Also I had to create a BlueFish object explicitly in the constructor of the main Jpanel in order to initialize the Map. I hope I don't sound picky if I say I am not completely satisfied. My goal is to set up the aquarium so that new species can be added with minimal effort. I find myself typing "BlueFish" 5 or 6 times to make a new class. I want to get that down to one. But again, thank you for your help. – laertiades Dec 17 '12 at 15:17
  • @YanickRochon Well my foot is in my mouth. I was loading the classes elsewhere, but I shouldn't assume that that would always be the case, especially in an answer on SO. Thanks for pointing this out. – rees Dec 17 '12 at 21:46
  • @JesseGoodfellow I posted a different solution using some of the same ideas in conjunction with the enum type. You may not find this completely satisfactory either, but it is another potential solution with different trade-offs than the others. Good luck! – rees Dec 18 '12 at 00:57
1

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.

Cameron Skinner
  • 51,692
  • 2
  • 65
  • 86
Jonathan Henson
  • 8,076
  • 3
  • 28
  • 52
0

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.

laertiades
  • 1,992
  • 2
  • 19
  • 26
  • As for finding classes in a package, check out http://stackoverflow.com/questions/520328/can-you-find-all-classes-in-a-package-using-reflection . The accepted answer even seems to show how to get subclasses of a given class. Drawback is, you apparently need a third-party library. – cHao Dec 18 '12 at 04:36