【问题标题】:Builder Pattern with Generics and a static inner Factory on the extended class具有泛型的生成器模式和扩展类上的静态内部工厂
【发布时间】:2014-04-03 11:32:33
【问题描述】:

我正在尝试实现生成 Apache XmlBeans 对象的构建器模式。

我已经为我的所有类生成了构建器,它们继承自:

public abstract class Builder<T extends XmlObject> {

    protected T xmlObject;

    @SuppressWarnings("unchecked")
    public T build() {
        return (T) xmlObject.copy();
    }
}

然后我创建了几个像这样的构建器(Time2Save 继承自 XmlObject):

public class Time2SaveBuilder extends Builder<Time2Save> {
    public Time2SaveBuilder(int amount, String unit) {
        xmlObject = Time2Save.Factory.newInstance();
        xmlObject.setUnit(unit);
        xmlObject.setValue(amount);
    }
}

它工作得很好。但是我遇到的问题是我真的不喜欢在每个构建器中实例化 xmlObject 的重复,如果可能的话,我宁愿在抽象的 Builder 类中这样做。所以我尝试将此构造函数添加到 Builder 类:

@SuppressWarnings("unchecked")
public Builder() {
    xmlObject =  (T) T.Factory.newInstance();
}

并留下这样的实现:

public class Time2SaveBuilder extends Builder<Time2Save> {
    public Time2SaveBuilder(int amount, String unit) {
        xmlObject.setUnit(unit);
        xmlObject.setValue(amount);
    }
}

问题是我收到以下错误:

Exception in thread "main" java.lang.ClassCastException:
    org.apache.xmlbeans.impl.values.XmlAnyTypeImpl cannot be cast to
    a.b.c.d.Time2SaveDocument$Time2Save

我认为XmlAnyTypeImpl 是调用静态XmlObject.Factory 而不是继承类(在本例中为Time2Save)的结果。我想知道为什么会发生这种情况(因为我打电话给T.Factory而不是XmlObject.Factory)如果有什么方法可以做到这一点,而无需在每个电话中重复工厂调用构建器实现。谢谢!

【问题讨论】:

标签: java generics builder xmlbeans


【解决方案1】:

假设所有构建的对象都有一个无参数构造函数(以及一堆其他假设),你可以使用this idiom

class Builder<SHAPE extends Shape> {

  public final SHAPE shape = build();

  @SuppressWarnings("unchecked")
  private SHAPE build() {
    try {
      ParameterizedType parent = 
             (ParameterizedType) getClass().getGenericSuperclass();   
      Class<?> arg = (Class<?>) parent.getActualTypeArguments()[0];
      return (SHAPE) arg.newInstance();
    } catch (ReflectiveOperationException e) {
      throw new RuntimeException(e);
    }
  }

  public SHAPE shape() { return shape; }
}

但请注意,这可能会隐藏您的设计问题。

为什么你应该寻找替代品

当你知道如何使用静态方法时,它们会很方便,否则它们会咬你。在不使用反射的情况下,必须通过在源代码中命名封闭类来调用静态方法,从而使代码实际上无法重用。此外,静态方法不是虚拟的,即子类不能覆盖父行为 - 同样,您可以通过使用反射来作弊并自己调度调用。

为什么它不起作用

结构

public class Foo<T extends MyOuter> {
  public String bar() {
    return T.MyNested.aStaticMethod();
  }
}

访问类型变量的静态成员很少见 (demo)。方法签名在编译时被解析,因此T 的值没有机会被读取(即使是这样,类型参数也无法在 Java 中的编译步骤中存活)。搜索到的类型被递归解析,T 被替换为其上限,导致类名XmlObject.Factory

【讨论】:

  • 很棒的答案。非常感谢。
  • @Sirs 我删除了有关 JSL 的段落,因为它与本案无关。老实说,我不知道规范是否允许使用类型变量来访问静态成员。 Oracle 和 JDT 编译器都理解语法,但我们必须对其进行调查 :) 所以,感谢您的提问
【解决方案2】:

创建一个从构造函数调用的抽象方法怎么样?就这样

public abstract class Builder<T extends XmlObject> {

   abstract protected T createObject();

   @SuppressWarnings("unchecked")
   public Builder() {
     xmlObject = (T) createObject();
   }
}

然后在 Time2SaveBuilder 中,您只需实现它即可返回正确的工厂实例。这应该可以解决问题

【讨论】:

  • 我试图避免在每个 Builder 实现上显式调用工厂方法......你的答案没有解决,因为我仍然必须在每个实现上调用工厂(而且有很多)。我希望能够从抽象构建器中调用它,或者至少了解 Java 泛型阻止我的实现正常运行的原因......
  • 在 ctor 中调用非 final 方法是不好的做法,因为它会在孩子的 ctor 之前运行,因此可能会发现对象处于不一致的状态。可以在文档中对此提出警告,但由于它处于设计阶段,我认为您应该寻找替代方案
猜你喜欢
  • 2020-09-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多