生成器(Joshua Bloch风格)用于抽象类的具体实现吗? [英] Builder (Joshua Bloch-style) for concrete implementation of abstract class?

查看:83
本文介绍了生成器(Joshua Bloch风格)用于抽象类的具体实现吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有一个抽象类(BaseThing)。它具有一个必需参数(基本必需)和一个可选参数(基本可选)。我有一个扩展它的具体类(事物)。它还具有一个必需参数( required)和一个可选参数( optional)。像这样的东西:

 公共抽象类BaseThing {
public static final String DEFAULT_BASE_OPTIONAL = Default Base Optional;

private final字符串baseRequired;
private String baseOptional = DEFAULT_BASE_OPTIONAL;

受保护的BaseThing(最终字符串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();
}

受保护的抽象void selfDescribeHook();
}

和:

  Public final class事物扩展了BaseThing {
public static final String DEFAULT_OPTIONAL =默认可选;

私人最终字符串;
private字符串可选= DEFAULT_OPTIONAL;

事物(最终字符串theRequired,最终字符串theBaseRequired){
super(theBaseRequired);
required =必需;
}

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

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

我想要一个Joshua Bloch风格的建筑工具东西。不过,更笼统地说,我想让BaseThing的具体实现更容易具有构建器,因此,我真正想要的(我认为)是可以轻松用于构建ThingBuilder,OtherThingBuilder或SuperThingBuilder的BaseThing构建器。 。



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

 公共抽象类BaseThingBuilder< T扩展BaseThing> {
private String baseOptional = BaseThing.DEFAULT_BASE_OPTIONAL;

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

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

返回t;
}

受保护的抽象T buildHook();
}

和:

 公共最终类ThingBuilder扩展了BaseThingBuilder< Thing>。 {
private final String baseRequired;
私人最终字符串;
private字符串可选= Thing.DEFAULT_OPTIONAL;

公共ThingBuilder(最终字符串theRequired,最后
最终字符串theBaseRequired){
required = TheRequired;
baseRequired = TheBaseRequired;
}

public ThingBuilder setOptional(final String value){
optional = value;
返回;
}

受保护的事物buildHook(){
事物=新事物(必需,baseRequired);
something.setOptional(可选);

退货;
}
}

可以用来构建Thing对象类似于以下内容:

  BaseThingBuilder< Thing> builder = 
新的ThingBuilder( Required!, Base Required!)
.setOptional( Optional!)
.setBaseOptional( Base Optional!);
事物= builder.build();
something.selfDescribe();

哪些输出:

 基本要求:基本要求! 
基本可选:基本可选!
必需:必需!
可选:可选!

我知道一个问题,但我认为它不是特别重要(尽管可以进行改进(这样做会很不错))是您必须在设置任何基本选项之前设置所有非基本选项:否则会导致语法错误,因为setBaseOptional()返回的是BaseThingBuilder而不是ThingBuilder。 / p>

预先感谢。

解决方案

我不认为这是一个这样考虑建造者的好主意。构建器的层次结构通常会导致头痛和脆弱的代码。



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

 公共界面生成器< T> {
T build();
}

public class Person {
private final字符串名称;

//使用生成器的正确方法是将一个实例传递给
//使用该生成器创建的类...
Person(PersonBuilder builder) {
this.name = builder.name;
}

public String getName(){返回名称; }

公共静态类PersonBuilder实现了Builder< Person> {
专用字串名称;
Public PersonBuilder name(String name){this.name = name;返回这个}

public Person build(){
if(name == null){
throw new IllegalArgumentException(必须指定名称);
}
返回新的Person(this);
}
}
}

Groovy,宝贝!怎么办?也许您想添加一个班级来代表学生。你是做什么?扩展人吗?当然可以。如何走一条更奇怪的路线并尝试聚合?是的,您也可以这样做...您的选择将对最终实现构建器的方式产生影响。假设您坚持传统做法并扩展了Person(您应该已经开始问自己, Person 成为具体的类有意义吗?如果我将其抽象化,我真的需要一个构建器吗?如果该类是抽象的,构建器应该是抽象的吗?):

 公共类学生扩展Person { 
私人最终长ID;

学生(StudentBulder构建器){
super(构建器);
this.id = builder.id;
}

public long getId(){返回ID; }

//不需要泛型,这将起作用:
公共静态类StudentBuilder扩展PersonBuilder {
private long id;
Public StudentBuilder id(long id){this.id = id;返回这个}

公共学生build(){
if(id< = 0){
throw new IllegalArgumentException(必须指定ID);
}
返回新学生(this);
}
}
}

好,这看起来像你想要什么!因此,您可以尝试:

 人员p =新的PersonBuilder()。name( John Doe)。build(); 
Student s = new StudentBuilder()。name( Jane Doe)。id(165).build();

看起来很棒!除此以外,它不会编译...第2行出现错误,并指出类型Person.PersonBuilder 的方法id(int)未定义。问题是 PersonBuilder#name 返回的生成器类型为 PersonBuilder ,这不是您想要的。在 StudentBuilder 中,您实际上希望 name 的返回类型为 StudentBuilder 。现在,您要预先考虑并意识到,如果 StudentBuilder 有任何扩展,您希望它完全返回其他内容……这可行吗?是的,使用泛型。但是,这很丑陋,并引入了很多复杂性。因此,我拒绝发布说明它的代码,因为担心有人会看到此线程并在其软件中实际使用它。



您可能会认为重新排列方法调用将起作用(在调用名称之前先调用 id ): new StudentBuilder()。id(165 ).name( Jane Doe)。build(),但不会。至少在没有明确转换为 Student 的情况下:(Student)new StudentBuilder()。id(165).name( Jane Doe)。 build(),因为在这种情况下,将调用 PersonBuilder#build ,其返回类型为 Person ...这简直是无法接受的!即使它在没有显式强制转换的情况下工作,也应该让您有所畏惧,知道必须按特定顺序调用构建器的方法。因为如果您不这样做,那么某些东西将无法工作...



如果您继续尝试使其工作,将会出现更多的问题。即使您确实可以使用它,我也不认为它很容易理解,而且肯定也不美观。当然,请随时向我证明错误并在此处发布您的解决方案。



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



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



此外,请查看相关问题


Let's say I have an abstract class (BaseThing). It has one required parameter ("base required") and one optional parameter ("base optional"). I have a concrete class that extends it (Thing). It also has one required parameter ("required") and one optional parameter ("optional"). So something like:

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

and:

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

I want to have a Joshua Bloch-style builder for Thing objects. More generally, though, I want to make it easy for concrete implementations of BaseThing to have builders, so what I really want (I think) is a BaseThing builder that can easily be used to make a ThingBuilder, or an OtherThingBuilder, or a SuperThingBuilder.

Is there a better way than the following that I've come up with (or are there problems with what I've come up with)?

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

and:

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

Which can be used to build Thing objects in a manner similarly to the following:

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

Which outputs:

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

One issue that I know about, but that I don't consider particularly important (though if it can be improved it would be nice to do so) is that you have to set all non-base options before you set any base option: Doing otherwise would result in a syntax error, as setBaseOptional() returns a BaseThingBuilder rather than a ThingBuilder.

Thanks in advance.

解决方案

I don't think it's a good idea to think of builders that way. A hierarchy of builders usually leads to headaches and fragile code.

Cutting down the amount of code that needs to be written in the concrete builders and reusing logic from the base builder is closely tied to the domain. It's not easy to develop a general solution. But, let's try to go through an example anyway:

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, baby! Now what? Maybe you want to add a class to represent a student. What do you do? Do you extend Person? Sure, that's valid. How about taking a more "strange" route and attempting aggregation? Yep, you can do that too... Your choice would have an affect on how you will end up implementing builders. Let's say you stick to the traditional path and extend Person (you should already starting asking yourself, does it make sense for Person to be a concrete class? If I make it abstract, do I really need a builder? If the class is abstract should the builder be abstract?):

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

Ok, this looks exactly like what you wanted! So, you try it:

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

Looks great! Except, it doesn't compile... There's an error at line 2 and it states The method id(int) is undefined for the type Person.PersonBuilder. The problem is that PersonBuilder#name returns a builder of type PersonBuilder, which isn't what you want. In StudentBuilder you actually want the return type of name to be StudentBuilder. Now, you think ahead and realize that if anything extends StudentBuilder you'd want it to return something else entirely... Is that doable? Yes, with generics. However, it's ugly as hell and introduces quite a bit of complexity. Therefore, I refuse to post the code that illustrates it, for the fear that someone will see this thread and actually use it in their software.

You might think rearranging method calls will work (calling id before calling name): new StudentBuilder().id(165).name("Jane Doe").build(), but it won't. At least not without an explicit cast to Student: (Student)new StudentBuilder().id(165).name("Jane Doe").build() since, in this case, PersonBuilder#build is being called which has a return type of Person... This is simply unacceptable! Even if it worked without an explicit cast, it should make you wince to know that a builder's methods must be called in a certain order. Because if you don't, something won't work...

There are many more problems that would arise if you continue trying to get it to work. And even if you did get it to work, I don't think it would be easily comprehensible and certainly not elegant. Of course, feel free to prove me wrong and post your solution here.

By the way, you should also ask yourself what is an abstract builder? Because, it sounds like an oxymoron.

In the end, I believe that the scope of this question is too great. The answer is domain-specific and hard to come up with in the absence of your requirements. Just remember, the general guideline for builders is to have them be as simple as possible.

Also, take a look at a related question.

这篇关于生成器(Joshua Bloch风格)用于抽象类的具体实现吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆