Java 具有子分类和所需参数的生成器设计模式?

Java 具有子分类和所需参数的生成器设计模式?,java,design-patterns,builder,Java,Design Patterns,Builder,最近我遇到了一个构建器模式非常强大的情况,但是我需要子类化。我查找了一些解决方案,一些建议使用泛型,而另一些建议使用普通子类。但是,我所看到的示例中没有一个具有必填字段,以便开始构建对象。我写了一个小例子来说明我的困境。每次我都会遇到一大堆问题,比如返回错误的类、无法重写静态方法、返回super()返回错误的数据类型等等。我感觉除了过度使用泛型之外,别无出路 在这种情况下,正确的方法是什么 测试员 import person.Person; import person.Student; pub

最近我遇到了一个构建器模式非常强大的情况,但是我需要子类化。我查找了一些解决方案,一些建议使用泛型,而另一些建议使用普通子类。但是,我所看到的示例中没有一个具有必填字段,以便开始构建对象。我写了一个小例子来说明我的困境。每次我都会遇到一大堆问题,比如返回错误的类、无法重写静态方法、返回super()返回错误的数据类型等等。我感觉除了过度使用泛型之外,别无出路

在这种情况下,正确的方法是什么

测试员

import person.Person;
import person.Student;

public class Tester
{
    public static void main(String[] args)
    {
        Person p = Person.builder("Jake", 18).interest("Soccer").build();
        // Student s = Student.builder(name, age) <-- It's weird that we still have access to pointless static method
        // Student s = Student.builder("Johnny", 24, "Harvard", 3).address("199 Harvard Lane") <-- returns Person builder, not student
        Student s = ((Student.Builder)Student.builder("Jack", 19, "NYU", 1).address("Dormitory")).build(); // really bad
    }
}
导入人.人;
进口人。学生;
公共类测试员
{
公共静态void main(字符串[]args)
{
Person p=Person.builder(“Jake”,18).interest(“Soccer”).build();
//Student s=Student.builder(姓名、年龄)您在这里写的内容:

Student s = ((Student.Builder)Student.builder("Jack", 19, "NYU", 1).address("Dormitory")).build(); // really bad
确实不是很自然,你不需要施展。
我们期望的是:

Student s = Student.builder("Jack", 19, "NYU", 1).address("Dormitory")).build(); 
除了在
Student.Builder
的实现中执行的所有强制转换之外,还有可能在运行时失败的噪音和语句:

    /* Things begins to get very messy here */
    public Builder subject(String subject)   {
        ((Student)reference).subjects.add(subject);          
        return this;
    }

    @Override public Student build() {          
        return (Student)super.build();
    }
您的主要问题是
Builder
类和构建方法之间的耦合。
要考虑的一个重要问题是,在编译时,方法绑定(编译器所选择的方法)是根据调用的目标的声明类型和其声明的声明类型执行的。 只有在应用动态绑定时,才会在运行时考虑实例化的类型:调用在编译时对运行时对象绑定的方法。

因此,在
Student.Builder
中定义的覆盖是不够的:

@Override public Student build() {
    return (Student)super.build();
}
在调用时:

Student.builder("Jack", 19, "NYU", 1).address("Dormitory").build();
编译时,
address(“宿舍”)
返回一个类型为
Person.Builder
的变量,因为方法在
Person.Builder
中定义:

public Builder address(String address){
    reference.address = address;
    return this;
}
并且它不会在
Student.Builder
中被覆盖
在编译时,对声明为
Person.Builder
的变量调用
build()
,将返回声明类型为a
Person
的对象,方法在
Person.Builder
中声明为:

public Person build(){
    return reference;
}
当然,在运行时,返回的对象将是
Student
as

Student.builder(“Jack”,19,“NYU”,1)
在引擎盖下创建了一个
学生
,而不是

为了避免从实现和客户端强制转换到
Student.builder
,请支持组合而不是继承:

public static class Builder {

  Person.Builder personBuilder;
  private Student reference;

  public Builder(final Student reference) {
    this.reference = reference;
    personBuilder = new Person.Builder(reference);
  }

  public Builder subject(String subject) {
    reference.subjects.add(subject);
    return this;
  }

  // delegation to Person.Builder but return Student.Builder
  public Builder interest(String interest) {
    personBuilder.interest(interest);
    return this;
  }

  // delegation to Person.Builder but return Student.Builder
  public Builder address(String address) {
    personBuilder.address(address);
    return this;
  }

  public Student build() {
    return (Student) personBuilder.build();
  }

}
你现在可以写:

Student s = Student.builder("Jack", 19, "NYU", 1)
                   .address("Dormitory")
                   .build(); 
甚至是:

Student s2 = Student.builder("Jack", 19, "NYU", 1)
                    .interest("Dance")
                    .address("Dormitory")
                    .build();
组合通常引入更多的代码作为继承,但它使代码 更强健,适应性更强。

作为旁注,您的实际问题与我1个月前回答的另一个问题非常接近。
你可能会感兴趣。

一些想法作为背景

  • 静态方法不是很好, 它们使得单元测试更加困难
  • 将构建器作为一个静态的嵌套类是可以的,但是如果您使用构建器来构建一个类,那么应该使该构建器不公开
  • 我更喜欢让构建器成为同一个包中的一个独立类,并使构造函数(由构建器创建的类的构造函数)可以访问包
  • 限制生成器构造函数参数
  • 我不喜欢为构建器使用类层次结构
  • Person和Student类各有一个生成器
  • 一些代码

    公共类PersonBuilder
    {
    私有字符串地址;
    私人互联网;
    私人最终利益清单;
    私有字符串名称;
    公共人事建设者()
    {
    interestList=新的LinkedList();
    }
    公共无效附加测试(
    最终字符串(新值)
    {
    //StringUtils是一个apache实用程序。
    if(StringUtils.isNotBlank(newValue))
    {
    兴趣列表。添加(newValue);
    }
    归还这个;
    }
    公众人物塑造()
    {
    //在此处执行验证。
    //检查所需的值:年龄和姓名。
    //发送构造函数中的所有参数。它不是公共的,所以这很好。
    返回新人员(地址、年龄、兴趣列表、姓名);
    }
    公共地址(
    最终字符串(新值)
    {
    地址=新值;
    归还这个;
    }
    公共人事设置(
    最终整数(新值)
    {
    年龄=新价值;
    归还这个;
    }
    公共人员生成器设置兴趣列表(
    最终列表(新值)
    {
    interestList.clear();
    if(CollectionUtils.isNotEmpty(newValue))
    {
    兴趣列表.addAll(newValue);
    }
    归还这个;
    }
    公共PersonBuilder集合名(
    最终字符串(新值)
    {
    name=newValue;
    归还这个;
    }
    }
    公共阶层人士
    {
    私人()
    {
    }
    人(
    最终字符串地址值,
    最终int ageValue,
    最终列表兴趣列表值,
    最终字符串名)
    {
    //准备好东西。
    //可选参数的句柄为null。
    }
    //创建获取或字段,但不创建集。只有生成器可以在类中设置值。
    }
    
    The,但需要一些上下文来指示返回哪个实现。保留基类的构造函数,但如果
    setSchool()
    setYear()
    addSubject()
    被调用,然后返回一个Student而不是Person。构建器模式的一个值是您不会得到一堆构造函数参数。
    Student s = Student.builder("Jack", 19, "NYU", 1)
                       .address("Dormitory")
                       .build(); 
    
    Student s2 = Student.builder("Jack", 19, "NYU", 1)
                        .interest("Dance")
                        .address("Dormitory")
                        .build();
    
    public class PersonBuilder
    {
        private String address;
        private int age;
        private final List<String> interestList;
        private String name;
    
        public PersonBuilder()
        {
            interestList = new LinkedList<>();
        }
    
        public void addInterest(
            final String newValue)
        {
            // StringUtils is an apache utility.
            if (StringUtils.isNotBlank(newValue))
            {
                interestList.add(newValue);
            }
    
            return this;
        }
    
        public Person build()
        {
            // perform validation here.
            // check for required values: age and name.
    
            // send all parameters in the constructor.  it's not public, so that is fine.
            return new Person(address, age, interestList, name);
        }
    
        public PersonBuilder setAddress(
            final String newValue)
        {
            address = newValue;
    
            return this;
        }
    
        public PersonBuilder setAge(
            final int newValue)
        {
            age = newValue;
    
            return this;
        }
    
        public PersonBuilder setInterestList(
            final List<String> newValue)
        {
            interestList.clear();
    
            if (CollectionUtils.isNotEmpty(newValue))
            {
                interestList.addAll(newValue);
            }
    
            return this;
        }
    
        public PersonBuilder setName(
            final String newValue)
        {
            name = newValue;
    
            return this;
        }
    }
    
    
    public class Person
    {
        private Person()
        {
        }
    
        Person(
            final String addressValue,
            final int ageValue,
            final List<String> interestListValue,
            final String name)
        {
            // set stuff.
            // handle null for optional parameters.
        }
    
        // create gets or the fields, but do not create sets.  Only the builder can set values in the class.
    }