Создайте экземпляры, используя один общий фабричный метод - PullRequest
5 голосов
/ 09 февраля 2011

Я пытаюсь найти простой способ создания объектов во время выполнения на основе статического атрибута класса String, называемого NAME.

Как я могу улучшить этот код, который использует простую конструкцию if?

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 () не может использоваться в этих классах, если я не удалю аргумент конструктора. Должен ли я создать карту (Map) всех поддерживаемых ссылок на классы цветов и переместить аргумент contructor в метод установки свойств, или есть другие простые решения?

Справочная информация: моя цель состоит в том, чтобы реализовать некоторую «саморегистрацию» новых классов Flower к FlowerFactory.getInstance().register(this.NAME, this.class), что означает, что из очень хороших ответов пока что решения на основе самоанализа подойдут лучше всего. *

Ответы [ 7 ]

4 голосов
/ 09 февраля 2011

Одной из возможностей будет использование enum. На простейшем уровне вы могли бы заменить константы, такие как Rose.NAME, на значения перечисления и поддерживать внутреннее отображение между значениями перечисления и классами для создания экземпляра:

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

Поскольку классы классов цветов не имеют конструктора по умолчанию, Class.newInstance() использовать нельзя, поэтому создание экземпляра класса с помощью отражения немного более громоздко (хотя и возможно). Альтернативой может быть использование Prototype для создания нового экземпляра цветка.

Это уже гарантирует, что вы всегда будете поддерживать соответствие между возможными названиями цветов и фактическими классами цветов в синхронизации. Когда вы добавляете новый класс цветка, вы должны создать новое значение перечисления, которое включает в себя отображение для создания новых экземпляров класса. Однако проблема с подходом enum заключается в том, что используемый вами экземпляр Garden исправлен при запуске. (Если вы не передадите его в качестве параметра getFlower() - но тогда есть риск потери согласованности, т. Е. Будет труднее обеспечить создание определенной группы цветов в определенном саду).

Если вы хотите быть еще более гибким, вы можете рассмотреть возможность использования Spring для перемещения всего отображения между именами и конкретными (bean) классами в файл конфигурации. Затем ваша фабрика просто загружает Spring ApplicationContext в фоновом режиме и использует определенное в нем отображение. Всякий раз, когда вы вводите новый подкласс цветка, вам просто нужно добавить новую строку в файл конфигурации. Опять же, однако, этот подход, в его самой простой форме, требует, чтобы вы исправили экземпляр компонента Garden во время конфигурации.

Если вы хотите переключаться между разными садами во время выполнения и обеспечивать согласованность между садами и группами цветов, Фабрика, использующая внутреннюю карту имен для классов цветов, может быть лучшим выбором. Принимая во внимание, что само сопоставление может быть снова сохранено в конфигурации, но вы можете создавать экземпляры отдельных экземпляров фабрики с различными экземплярами Garden во время выполнения.

3 голосов
/ 09 февраля 2011

Вы можете использовать отражение, несмотря на наличие аргумента конструктора:

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

В сочетании со статическим сопоставлением имени и класса это можно реализовать следующим образом:

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

где цветы могут быть заполнены в статическом блоке инициализатора:

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);
}
2 голосов
/ 09 февраля 2011

Вы можете использовать перечисление с абстрактным фабричным методом:

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

Я не могу сказать, является ли это лучшим способом в вашем случае, поскольку у меня мало информации.

1 голос
/ 09 февраля 2011

Помимо использования enum или отображения вы можете использовать отражение, если существует простое отображение имени на класс.

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.
   }
}
0 голосов
/ 09 февраля 2011

Если все ваши Flower имеют одинаковую сигнатуру конструктора, вы можете использовать отражение, чтобы установить параметр в конструкторе.

Очевидно, что это входит в сферу внедрения зависимостей, но, возможно, это то, что вы делаете:)

Если в вашем конструкторе много разных параметров, и если это безопасно, вы можете указать тип каждого параметра для поиска экземпляра для передачи, что немного похоже на то, что делает Guice.

0 голосов
/ 09 февраля 2011

Вы также можете сделать это, сохраняя имена строк на карте, чтобы избежать последовательности if / elses.

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

Если у вас есть полный контроль над вашими классами, вы можете выполнять создание экземпляров, используя отражение непосредственно от имени строки, например,

Class.forName(name);

Помимо этого, вы также можете попробовать инфраструктуру внедрения зависимостей. Некоторые из них предоставляют возможность извлекать экземпляр объекта из строкового имени.

0 голосов
/ 09 февраля 2011

Я бы предложил удалить состояние из фабричного объекта и передать объект Garden в качестве аргумента в методе статической фабрики:

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