mjn
mjn

Reputation: 36654

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.

Upvotes: 6

Views: 3621

Answers (7)

Péter Török
Péter Török

Reputation: 116266

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.

Upvotes: 4

Puce
Puce

Reputation: 38132

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.

Upvotes: 2

Peter Lawrey
Peter Lawrey

Reputation: 533500

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

Upvotes: 1

sfussenegger
sfussenegger

Reputation: 36095

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

Upvotes: 3

dimitrisli
dimitrisli

Reputation: 21391

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

Upvotes: 0

Rich
Rich

Reputation: 15757

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.

Upvotes: 0

Johan Sj&#246;berg
Johan Sj&#246;berg

Reputation: 49187

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.

Upvotes: 0

Related Questions