Builder(Joshua Bloch-style)具体实现抽象类?

时间:2012-11-06 17:11:51

标签: java abstract builder concrete

假设我有一个抽象类(BaseThing)。它有一个必需参数(“base required”)和一个可选参数(“base optional”)。我有一个扩展它的具体类(Thing)。它还有一个必需参数(“必需”)和一个可选参数(“可选”)。如下所示:

public abstract class BaseThing {
    public static final String DEFAULT_BASE_OPTIONAL = "Default Base Optional";

    private final String baseRequired;
    private String baseOptional = DEFAULT_BASE_OPTIONAL;

    protected BaseThing(final String theBaseRequired) {
        this.baseRequired = theBaseRequired;
    }

    final void setBaseOptional(final String newVal) {
        this.baseOptional = newVal;
    }

    public final void selfDescribe() {
        System.out.println("Base Required: " + baseRequired);
        System.out.println("Base Optional: " + baseOptional);

        selfDescribeHook();
    }

    protected abstract void selfDescribeHook();
}

public final class Thing extends BaseThing {
    public static final String DEFAULT_OPTIONAL = "Default Optional";

private final String required;
    private String optional = DEFAULT_OPTIONAL;

    Thing(final String theRequired, final String theBaseRequired) {
        super(theBaseRequired);
        required = theRequired;
    }

    @Override
    protected void selfDescribeHook() {
        System.out.println("Required: " + required);
        System.out.println("Optional: " + optional);
    }

    void setOptional(final String newVal) {
        optional = newVal;
    }
}

我想为Thing对象设置一个Joshua Bloch风格的构建器。但更一般地说,我想让BaseThing的具体实现很容易拥有构建器,所以我真正想要的(我认为)是一个BaseThing构建器,它可以很容易地用于制作ThingBuilder,OtherThingBuilder或SuperThingBuilder。

有没有比我提出的更好的方法(或者我提出的方法有问题)?

public abstract class BaseThingBuilder<T extends BaseThing> {
    private String baseOptional = BaseThing.DEFAULT_BASE_OPTIONAL;

    public BaseThingBuilder<T> setBaseOptional(final String value) {
        baseOptional = value;
        return this;
    }

    public T build() {
        T t = buildHook();
        t.setBaseOptional(baseOptional);

        return t;
    }

    protected abstract T buildHook();
}

public final class ThingBuilder extends BaseThingBuilder<Thing> {
    private final String baseRequired;
    private final String required;
    private String optional = Thing.DEFAULT_OPTIONAL;

    public ThingBuilder(final String theRequired,
            final String theBaseRequired) {
        required = theRequired;
        baseRequired = theBaseRequired;
    }

    public ThingBuilder setOptional(final String value) {
        optional = value;
        return this;
    }

    protected Thing buildHook() {
        Thing thing = new Thing(required, baseRequired);
        thing.setOptional(optional);

        return thing;
    }
}

可以用类似于以下方式构建Thing对象:

        BaseThingBuilder<Thing> builder = 
                new ThingBuilder("Required!", "Base Required!")
                    .setOptional("Optional!")
                    .setBaseOptional("Base Optional!");
        Thing thing = builder.build();
        thing.selfDescribe();

哪个输出:

Base Required: Base Required!
Base Optional: Base Optional!
Required: Required!
Optional: Optional!

我所知道的一个问题,但我认为并不特别重要(虽然如果可以改进它会很好)这是你必须在设置任何基础之前设置所有非基本选项选项:否则会导致语法错误,因为setBaseOptional()返回BaseThingBuilder而不是ThingBuilder。

提前致谢。

2 个答案:

答案 0 :(得分:3)

我认为以这种方式思考建筑商并不是一个好主意。构建器的层次结构通常会导致头痛和脆弱的代码。

减少需要在具体构建器中编写的代码量以及从基础构建器重用逻辑与域密切相关。开发通用解决方案并不容易。但是,让我们试着通过一个例子:

public interface Builder<T> {
  T build();
}

public class Person {
  private final String name;

  //the proper way to use a builder is to pass an instance of one to
  //the class that is created using it...
  Person(PersonBuilder builder) {
    this.name = builder.name;
  }

  public String getName(){ return name; }

  public static class PersonBuilder implements Builder<Person> {
    private String name;
    public PersonBuilder name(String name){ this.name = name; return this; }

    public Person build() {
      if(name == null) {
        throw new IllegalArgumentException("Name must be specified");
      }
      return new Person(this);
    }
  }
}

Groovy,宝贝!怎么办?也许你想添加一个代表学生的课程。你是做什么?你扩展人吗?当然,这是有效的。如何采取更“奇怪”的路线并尝试聚合?是的,您也可以这样做......您的选择会对您最终如何实施构建器产生影响。让我们说你坚持传统的路径并扩展Person(你应该已经开始问自己了,Person成为一个具体的类是否有意义?如果我把它抽象化,我真的需要一个建设者吗?如果如果构建器是抽象的,那么class是抽象的吗?):

public class Student extends Person {
  private final long id;

  Student(StudentBulder builder) {
    super(builder);
    this.id = builder.id;
  }

  public long getId(){ return id; }

  //no need for generics, this will work:
  public static class StudentBuilder extends PersonBuilder {
    private long id;
    public StudentBuilder id(long id){ this.id = id; return this; }

    public Student build() {
      if(id <= 0) {
        throw new IllegalArgumentException("ID must be specified");
      }
      return new Student(this);
    }
  }
}

好的,这看起来很像你想要的!所以,你试试看:

Person p = new PersonBuilder().name("John Doe").build();
Student s = new StudentBuilder().name("Jane Doe").id(165).build();

看起来很棒!除外,它没有编译......第2行有一个错误,它表示The method id(int) is undefined for the type Person.PersonBuilder。问题是PersonBuilder#name返回类型PersonBuilder的构建器,这不是您想要的。在StudentBuilder中,您确实希望name的返回类型为StudentBuilder。现在,你提前思考并意识到,如果有任何扩展StudentBuilder,你会希望它完全归还其他东西......这是可行的吗?是的,有了泛型。然而,它很丑陋,引入了相当多的复杂性。因此,我拒绝发布说明它的代码,因为担心有人会看到这个帖子并且实际上在他们的软件中使用它。

您可能认为重新排列方法调用会起作用(在调用id之前调用name):new StudentBuilder().id(165).name("Jane Doe").build(),但它不会。至少没有明确转换为Student(Student)new StudentBuilder().id(165).name("Jane Doe").build(),因为在这种情况下,正在调用PersonBuilder#build,其返回类型为Person ...这只是不能接受的!即使它没有显式强制转换,它也应该让你知道必须以某种顺序调用构建器的方法。因为如果你不这样做,某些东西就行不通了......

如果你继续尝试让它发挥作用,会出现更多问题。即使你确实让它发挥作用,我也不认为这很容易理解,也不会优雅。当然,请随意证明我的错误并在此处发布您的解决方案。

顺便说一句,您还应该问自己什么是抽象构建器?因为,这听起来像是矛盾的。

最后,我认为这个问题的范围太大了。答案是针对特定领域的,如果没有您的要求,很难提出。请记住,建造者的一般准则是让它们尽可能简单。

另外,请查看related question

答案 1 :(得分:0)

据我所知,如果你删除了泛型,那么

BaseThingBuilder<Thing> builder = 
            new ThingBuilder("Required!", "Base Required!")

更改为

BaseThingBuilder builder = 
            new ThingBuilder("Required!", "Base Required!")

其余部分都保持不变,包括必须首先初始化子类的限制。所以我真的认为这不保证使用泛型。也许我错过了什么。

我似乎记得Bjarne Stroustrup这样的事情,很久以后......