Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create instances using one generic factory method

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.

like image 671
mjn Avatar asked Feb 09 '11 15:02

mjn


4 Answers

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.

like image 171
Péter Török Avatar answered Oct 23 '22 07:10

Péter Török


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);
}
like image 28
sfussenegger Avatar answered Oct 23 '22 07:10

sfussenegger


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.

like image 32
Puce Avatar answered Oct 23 '22 08:10

Puce


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.
   }
}
like image 21
Peter Lawrey Avatar answered Oct 23 '22 08:10

Peter Lawrey