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

发布于 2024-10-17 01:25:57 字数 718 浏览 2 评论 0原文

我试图找到一种易于扩展的方法来在运行时基于静态 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),并将构造函数参数移至属性设置器方法,还是还有其他简单的解决方案?

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

耳钉梦 2024-10-24 01:25:58

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

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

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.
   }
}
黎夕旧梦 2024-10-24 01:25:58

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

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

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

Class.forName(name);

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

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.

牵你的手,一向走下去 2024-10-24 01:25:58

如果您的所有Flower具有相同的构造函数签名,您可以使用反射来设置构造函数上的参数。

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

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

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.

Saygoodbye 2024-10-24 01:25:58

我建议从工厂对象中删除状态并将您的 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;
}

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;
}
梨涡 2024-10-24 01:25:57

一种可能性是使用枚举。在最简单的层面上,您可以用枚举值替换 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 来创建新的花实例。

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

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

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

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.

满地尘埃落定 2024-10-24 01:25:57

尽管有构造函数参数,但您可以使用反射:

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

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);
}
心碎无痕… 2024-10-24 01:25:57

您可以将枚举与抽象工厂方法一起使用:

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

不过,我不能说这对于您的情况是否是最好的方法,因为我掌握的信息很少。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文