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