【问题标题】:Extending an abstract class with an abstract builder vs concrete class and concrete builder使用抽象构建器与具体类和具体构建器扩展抽象类
【发布时间】:2016-04-25 07:23:39
【问题描述】:

我想在我即将进行的一些工作中使用构建器模式,该工作在层次结构中有多个类。基类将至少有 9 个字段开始,各个子类可能会添加 2-4 个 more 字段。这很快就会失控,而建造者模式正是出于这个原因对我很有吸引力。我在书籍和文章中初步了解了构建器模式。他们很有帮助,但对如何扩展这种模式一无所知。我试图自己实现这一点,但是我遇到了每个子类的构造函数的问题,因为我不知道如何将构建器中收集的数据传递给超类。我在 SO 上寻找一些答案,这就是我发现的。

这个来自SO 24243240,其中给出了如何使用抽象构建器扩展抽象类的示例。也是基于这个blog post

public abstract class AbstractA {
protected String s;
protected int i;
protected AbstractA() {
}
protected abstract static class ABuilder<T extends AbstractA, B extends ABuilder<T,B>> {
    protected T object;
    protected B thisObject;
    protected abstract T getObject(); //Each concrete implementing subclass overrides this so that T becomes an object of the concrete subclass
    protected abstract B thisObject(); //Each concrete implementing subclass builder overrides this for the same reason, but for B for the builder
    protected ABuilder() {
        object = getObject();
        thisObject = thisObject();
    }
    public B withS(String s) {
        object.s = s;
        return thisObject;
    }
    public B withI(int i) {
        object.i = i;
        return thisObject;
    }
    public T build() {
        return object;
    }
}
}


public final class ConcreteA extends AbstractA {
    private String foo;
    protected ConcreteA() {
    }
    public static final class Builder extends AbstractA.ABuilder<ConcreteA,Builder> {
        @Override protected ConcreteA getObject() {
            return new ConcreteA();
        }
        @Override protected Builder thisObject() {
            return this;
        }
        public Builder() {
        }
        public Builder withFoo(String foo) {
            object.foo = foo;
            return this;
        }
    }
}

然后在客户端代码中,它看起来像......

ConcreteA baz = new ConcreteA.Builder().withFoo("foo").withS("bar").withI(0).build();

我喜欢这个示例,因为它允许您轻松地扩展这些类,但在我看来,这也违背了使用构建器模式的目的,因为方法 withS(String s)withI(int i) 的行为很像 setter 方法。此外,此方法将基类和构建器类的字段保留为受保护而不是私有。

这是来自SO 17164375的一封邮件

public class NutritionFacts {

    private final int calories;

    public static class Builder<T extends Builder> {

        private int calories = 0;

        public Builder() {}

        public T calories(int val) {
            calories = val;
            return (T) this;
        }

        public NutritionFacts build() { return new NutritionFacts(this); }
    }

    protected NutritionFacts(Builder builder) {
        calories = builder.calories;
    }
}

public class GMOFacts extends NutritionFacts {

    private final boolean hasGMO;

    public static class Builder extends NutritionFacts.Builder<Builder> {

        private boolean hasGMO = false;

        public Builder() {}

        public Builder GMO(boolean val) {
            hasGMO = val;
            return this;
        }

        public GMOFacts build() { return new GMOFacts(this); }
    }

    protected GMOFacts(Builder builder) {
        super(builder);
        hasGMO = builder.hasGMO;
    }
}

我喜欢这个似乎更接近 Josh Bloch 描述的构建器模式,它还允许您简单地将构建器传递给您想要实例化的类的构造器。这将是在构建器中进行一些验证的好方法实例化调用build() 中的对象。同时,这个例子展示了如何用具体类扩展构建器模式,当你这样做时,扩展具体类所带来的所有讨厌的可能性(例如不一致的接口,可能破坏状态的继承方法)你的对象等)

所以我的问题是有没有办法用抽象构建器实现抽象类,它还允许您在基类的构造器中传递对构建器的引用?比如:

public abstract BaseClass {
// various fields go here
...
    public abstract Builder<T extends BaseClass, B extends Builder<T,B>> {
    // add chaining methods here
    ...
    public T build() {
        if (isValid()) return new T(this);
        else Throw new IllegalArgumentException("Invalid data passed to builder.");
    }
    }
public BaseClass(Builder builder) {
    // set fields of baseclass here
}
}

我意识到你不能像我在这里展示的那样实例化一个对象,但是我的意思是还有其他方法吗?这可能是工厂去的地方吗?也许我只是对一般的构建器模式有错误的假设。 :) 如果是这样的话,有没有更好的方向?

【问题讨论】:

    标签: java generics inheritance design-patterns builder


    【解决方案1】:

    你的第一个例子还不错,但我认为它不是你要找的。​​p>

    我仍然有点不确定你到底想要什么,但看到你的例子不适合你,我想我会给你一两个我自己的。 :)

    class ParentBuilder{
        public ConcreteParent build(){
            ConcreteParent parent = new ConcreteParent();
            parent.setFirst(1);
            parent.setSecond(2);
            parent.setThird(3);
            return parent;
        }
    }
    
    class ChildBuilder{
        public ConcreteChild build(ParentBuilder parentBuilder){
            ConcreteParent parent = parentBuilder.build();
            ConcreteChild child = new ConcreteChild();
            child.setFirst(parent.getFirst());
            child.setSecond(parent.getSecond());
            child.setThird(parent.getThird());
            child.setFourth(4); //Child specific value
            child.setFifth(5); //Child specific value
            return child;
        }
    }
    

    任何新类型都会有自己的构建器,并接受其父级的构建器。 如您所见,这类似于:

            public NutritionFacts build() { return new NutritionFacts(this); }
        }
    
        protected NutritionFacts(Builder builder) {
            calories = builder.calories;
        }
    

    在你的例子中。

    然而,随着变量和子类的数量越来越多,这也很快失控了。

    另一种方法是使用动态变量,看看这个:http://martinfowler.com/apsupp/properties.pdf Martin Fowler 写了一篇很棒的文章,详细说明了所有的利弊。

    无论如何,这是我的第二个例子:

    public class Demo {
    
        public static void main(String[] args) {
            ConcreteBuilder builder = new ConcreteBuilder();
            Concrete concrete = builder.with("fourth", "valueOfFourth").build();
            for(String value : concrete.getAttributes().values())
                System.out.println(value);
        }
    }
    
    class ConcreteBuilder{
        private Concrete concrete;
    
        public ConcreteBuilder(){
            concrete = new Concrete();
        }
    
        public ConcreteBuilder with(String key, String value){
            concrete.getAttributes().put(key, value);
            return this;
        }
        public Concrete build(){
            return concrete;
        }
    }
    
    class Concrete{
        private HashMap<String, String> attributes;
    
        public Concrete(){
            attributes = new HashMap<>();
        }
    
        public HashMap<String, String> getAttributes(){
            attributes.put("first", "valueOfFirst");
            attributes.put("second", "valueOfSecond");
            attributes.put("third", "valueOfThird");
            return attributes;
        }
    }
    

    这里的神奇之处在于,您(可能)不再需要所有这些子类。 如果这些子类的行为没有改变,而只是它们的变量,那么使用这样的系统应该没问题。 我强烈建议您阅读有关该主题的 Martin Fowler 文章,这样做有好有坏,但我认为这是一篇好文章。

    我希望这能让你更接近答案,祝你好运。 :)

    【讨论】:

    • 我知道这有点晚了,但你的答案!当时我的主管告诉我要完全避免使用构建器模式,因为 Bean 需要具有 getter 和 setter 方法。我很天真,希望实施一个“聪明”的解决方案。
    猜你喜欢
    • 1970-01-01
    • 2012-05-15
    • 2020-08-26
    • 2014-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-12
    相关资源
    最近更新 更多