3

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

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。

提前致谢。

4

2 回答 2

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

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

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返回了一个 type 的构建器PersonBuilder,这不是你想要的。在StudentBuilder您实际上希望返回类型nameStudentBuilder. 现在,您提前思考并意识到,如果有任何扩展StudentBuilder,您会希望它完全返回其他东西……这可行吗?是的,使用泛型。然而,它非常丑陋,并且引入了相当多的复杂性。因此,我拒绝发布说明它的代码,因为担心有人会看到这个线程并在他们的软件中实际使用它。

您可能认为重新安排方法调用会起作用(在调用id之前调用name) :,new StudentBuilder().id(165).name("Jane Doe").build()但它不会。至少在没有明确转换为Student:(Student)new StudentBuilder().id(165).name("Jane Doe").build()的情况下并非如此,因为在这种情况下,PersonBuilder#build正在调用其返回类型为Person... 这简直是不可接受的!即使它在没有显式转换的情况下工作,它也应该让你畏缩,知道必须以特定顺序调用构建器的方法。因为如果你不这样做,有些事情就不会奏效......

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

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

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

另外,看看一个相关的问题

于 2012-11-06T19:28:22.880 回答
0

As far as I can tell if you remove the generics then

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

changes to

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

The rest of it all remains same, including the restriction that subclass has to be initialized first. So I really don't think this warrants use of generics. Maybe I am missing something.

I seem to remember something like this from Bjarne Stroustrup, long back...

于 2012-11-06T21:34:48.057 回答