有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java使用一个通用工厂方法创建实例

我试图找到一种易于扩展的方法,在运行时基于名为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()。我应该构建所有受支持的flower类引用的映射(map),并将constructor参数移动到属性setter方法,还是有其他简单的解决方案

背景信息:我的目标是通过FlowerFactory.getInstance().register(this.NAME, this.class)实现新Flower类的某种“自我注册”,这意味着从目前为止非常好的答案来看,基于内省的解决方案最适合


共 (6) 个答案

  1. # 1 楼答案

    一种可能是使用枚举。在最简单的级别上,可以用枚举值替换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;
        }
    }
    

    由于flower类没有默认构造函数,Class.newInstance()无法使用,因此通过反射实例化类有点麻烦(尽管可能)。另一种方法是使用Prototype创建新的flower实例

    这已经确保了可能的花名称和实际的花类之间的映射始终保持同步。添加新flower类时,必须创建新的枚举值,其中包括创建新类实例的映射。然而,enum aproach的问题是,您使用的Garden实例在启动时是固定的。(除非您将其作为参数传递给getFlower(),否则会有失去连贯性的风险,即更难确保在特定花园中创建特定的花组)

    如果您想更灵活,可以考虑使用Spring将名称和具体(bean)类之间的映射映射到配置文件。然后,您的工厂只需在后台加载一个Spring ApplicationContext,并使用其中定义的映射。每当你引入一个新的flower子类时,你只需要在配置文件中添加一个新行。不过,这种方法以其最简单的形式再次要求您在配置时修复Gardenbean实例

    如果您想在运行时在不同的花园之间切换,并确保花园和花卉组之间的一致性,那么使用内部名称映射到花卉类的工厂可能是最佳选择。虽然映射本身可以再次存储在配置中,但您可以在运行时用不同的Garden实例实例化不同的工厂实例

  2. # 2 楼答案

    如果所有的Flower都有相同的构造函数签名,那么可以使用反射在构造函数上设置参数

    显然,这正在进入依赖注入的领域,但也许这就是你正在做的:)

    如果构造函数中有很多不同的参数,如果这样做是安全的,那么可以选择每个参数的类型来查找要传入的实例,这有点像Guice所做的

  3. # 3 楼答案

    还可以通过将字符串名称存储在映射中来避免一系列if/else

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

    如果你完全控制你的类,你可以直接从字符串名中使用反射来执行实例化,例如

    Class.forName(name);
    

    除此之外,您还可以尝试依赖注入框架。其中一些提供了从字符串名称检索对象实例的功能

  4. # 4 楼答案

    可以将枚举与抽象工厂方法结合使用:

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

    不过,我不能说这是不是你最好的方式,因为我掌握的信息很少

  5. # 5 楼答案

    除了使用枚举或映射之外,如果存在名称到类的简单映射,还可以使用反射

    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.
       }
    }
    
  6. # 6 楼答案

    即使有构造函数参数,也可以使用反射:

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