Create instances using one generic factory method
Asked Answered
R

7

6

I am trying to find a easy to extend way to create objects at runtime based on a static String class attribute, called NAME.

How can I improve this code, which uses a simple if construct?

public class FlowerFactory {

private final Garden g;

public FlowerFactory(Garden g) {
  this.g = g;
}

public Flower createFlower(final String name) {
    Flower result = null;

   if (Rose.NAME.equals(name)) {
       result = new Rose(g);
   } else if (Oleander.NAME.equals(name)) {
       result = new Oleander(g);
   } else if ... { ... } ...

   return result;
}

newInstance() can not be used on these classes, unless I remove the constructor argument. Should I build a map (Map) of all supported flower class references, and move the contructor argument to a property setter method, or are there other simple solutions?

Background information: my goal is to implement some kind of 'self-registering' of new Flower classes, by FlowerFactory.getInstance().register(this.NAME, this.class), which means that from the very good answers so far the introspection-based solutions would fit best.

Rosaceous answered 9/2, 2011 at 15:30 Comment(0)
A
3

You can use reflection despite having a constructor argument:

Rose.class.getConstructor(Garden.class).newInstance(g);

Combined with a static name to class mapping, this could be implemented like this:

// TODO handle unknown name
FLOWERS.get(name).getConstructor(Garden.class).newInstance(g);

where flowers could be populated in a static initializer block:

static {
  Map<String, Class<? extends Flower>> map = new HashMap<String, Class<? extends Flower>>();
  map.put(Rose.NAME, Rose.class);
  // add all flowers
  FLOWERS = Collections.unmodifieableMap(map);
}
Aircondition answered 9/2, 2011 at 15:42 Comment(0)
S
4

One possibility would be using an enum. On the simplest level, you could replace constants like Rose.NAME with enum values, and maintain an internal mapping between enum values and classes to instantiate:

public enum Flowers {
    ROSE(Rose.class),
    OLEANDER(Oleander.class);

    private final Class<? extends Flower> flowerClass;

    Flowers(Class<? extends Flower> flowerClass) {
        this.flowerClass = flowerClass;
    }

    public Flower getFlower() {
        Flower flower = null;
        try {
            flower = flowerClass.newInstance();
        } catch (InstantiationException e) {
            // This should not happen
            assert false;
        } catch (IllegalAccessException e) {
            // This should not happen
            assert false;
        }
        return flower;
    }
}

Since the flower classes classes have no default constructor, Class.newInstance() can not be used, so instantiating the class via reflection is a bit more cumbersome (although possible). An alternative could be to use a Prototype to create the new flower instance.

This already ensures that you always keep the mapping between possible flower names and actual flower classes in sync. When you add a new flower class, you must create a new enum value, which includes the mapping to create new class instances. However, the problem with the enum aproach is that the Garden instance you use is fixed at startup. (Unless you pass it as a parameter to getFlower() - but then there is a risk of losing coherence, i.e. it is harder to ensure that a specific group of flowers is created in a specific garden).

If you want to be even more flexible, you may consider using Spring to move the whole mapping between names and concrete (bean) classes out to a configuration file. Your factory then simply loads a Spring ApplicationContext in the background and uses the mapping defined in it. Whenever you introduce a new flower subclass, you just need to add a new line to the config file. Again, though, this approach, in its simplest form, requires you to fix the Garden bean instance at configuration time.

If you want to switch between different gardens at runtime, and ensure consistency between gardens and groups of flowers, a Factory using an internal map of names to flower classes may be the best choice. Whereas the mapping itself can again be stored in configuration, but you can instantiate distinct factory instances with distinct Garden instances at runtime.

Schoening answered 9/2, 2011 at 15:37 Comment(4)
The mapping could be built into the enum perhapsGibrian
+1 but instead of an "internal mapping" there could be an abstract method Flower create(Garden g); that's implemented by all enum instances: ROSE { Flower create(Garden g) { return new Rose(g); }Aircondition
@sfussenegger, true. This gets rid of reflection, which is good; however, the cost is extra code to override the create() method in each enum value. If there are lots of enum values, this may bloat the code a lot.Norm
éter you're right. Though avoiding InstantiationException at runtime certainly is a core issue here. But using Class<?> as enum argument along with a simple unit test that calls getFlower() for all enum values would be sufficiently safe I guess.Aircondition
A
3

You can use reflection despite having a constructor argument:

Rose.class.getConstructor(Garden.class).newInstance(g);

Combined with a static name to class mapping, this could be implemented like this:

// TODO handle unknown name
FLOWERS.get(name).getConstructor(Garden.class).newInstance(g);

where flowers could be populated in a static initializer block:

static {
  Map<String, Class<? extends Flower>> map = new HashMap<String, Class<? extends Flower>>();
  map.put(Rose.NAME, Rose.class);
  // add all flowers
  FLOWERS = Collections.unmodifieableMap(map);
}
Aircondition answered 9/2, 2011 at 15:42 Comment(0)
S
2

You could use an enum with a abstract factory method:

public enum FlowerType{
  ROSE("rose"){
    public Rose createFlower(Garden g){
      return new Rose(g);
    }
  },
  OLEANDER("oleander"){
    public Oleander createFlower(Garden g){
      return new Oleander(g);
    }
  };
  private final static Map<String, FlowerType> flowerTypes = new HashMap<String, FlowerType>();
  static {
    for (FlowerType flowerType : values()){
      flowerTypes.put(flowerType.getName(), flowerType); 
  }
  private final String name;
  protected FlowerType(String name){
    this.name = name;
  }
  public String getName(){
    return name;
  }  
  public abstract Flower createFlower(Garden g);
  public static FlowerType getFlower(String name){
    return flowerTypes.get(name);
  }
}

I cannot say if this is the best way in your case, though, as I have to few information.

Sinew answered 9/2, 2011 at 15:55 Comment(0)
W
1

Apart from using an enum, or a mapping you could use reflection if there is a simple mapping of name to class.

public Flower createFlower(final String name) {
   try {
      Class clazz = Class.forName("mypackage.flowers."+name);
      Constructor con = clazz.getConstructor(Garden.class);
      return (Flower) con.newInstance(g);
   } catch (many exceptions) {
      throw new cannot create flower exception.
   }
}
Wolfram answered 9/2, 2011 at 15:46 Comment(0)
N
0

I would suggest removing the state from your factory object and pass your Garden object as an argument in the static factory method:

public class FlowerFactory {

private FlowerFactory() {}

public static Flower createFlower(final String name, Garden g) {
    Flower result = null;

   if (Rose.NAME.equals(name)) {
       result = new Rose(g);
   } else if (Oleander.NAME.equals(name)) {
       result = new Oleander(g);
   } else if ... { ... } ...

   return result;
}
Nudge answered 9/2, 2011 at 15:35 Comment(2)
@sfussenegger: that's the trade-off if you want to make the method staticNudge
true, but that's either the wrong answer or the wrong question ;)Aircondition
N
0

You could also do it by storing the string names in a map to avoid the series of if/elses.

Map<String, Class> map;
map.get(name).newInstance();

If you have full control over your classes you can perform instantiation using reflection directly from the string name, e.g.,

Class.forName(name);

Apart from this you could also try a dependency injection framework. Some of these provides the capability to retrieve an object instance from a string name.

Neoterize answered 9/2, 2011 at 15:38 Comment(2)
won't work as Flowers take a Garden as constructor element which is explicitly mentioned in the questionAircondition
Correct, assuming that's always the case, a less generic sample would include something like Class.getConstructor(Garden.class).newInstance(..)Anaxagoras
A
0

If all your Flowers have the same constructor signature you could use reflection to set the parameter on the constructor.

Obviously this is getting into the realms of dependency injection, but maybe that's what you're doing :)

If you have lots of different parameters in your constructor, if it is safe to do so, you could the type of each parameter to look up the instance to pass in, a bit like what Guice does.

Allomerism answered 9/2, 2011 at 15:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.