最近我遇到了构建器模式非常强大的情况,但我需要子类化。我查找了一些解决方案和一些建议的泛型,而其他人建议正常的子类。但是,我查看的所有示例都没有必填字段才能开始构建对象。我写了一个小例子来说明我陷入困境的地方。在每一个转弯我都会遇到问题,其中的东西会返回错误的类,不能覆盖静态方法,返回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
}
}
人类
package person;
import java.util.ArrayList;
import java.util.List;
public class Person
{
// Required
protected String name;
protected int age;
// Optional
protected List<String> interests = new ArrayList<>();
protected String address = "";
protected Person(String name, int age)
{
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
public List<String> getInterests() { return interests; }
public String getAddress() { return address; }
// person.person does not allow builder construction
// unless all required fields are provided
/* Problem: I have to repeat the constructor fields here, very annoying */
public static Builder builder(String name, int age)
{
Person p = new Person(name, age);
return new Builder(p);
}
public static class Builder
{
Person reference;
protected Builder(Person reference)
{
this.reference = reference;
}
public Builder address(String address)
{
reference.address = address;
return this;
}
public Builder interest(String interest)
{
reference.interests.add(interest);
return this;
}
public Person build()
{
return reference;
}
}
}
学生班
package person;
import java.util.ArrayList;
import java.util.List;
public class Student extends Person
{
// Required
protected String school;
protected int year;
// Optional
protected List<String> subjects = new ArrayList<>();
// This looks good
public Student(final String name, final int age, final String school, final int year)
{
super(name, age);
this.school = school;
this.year = year;
}
public String getSchool() { return school; }
public int getYear() { return year; }
public List<String> getSubjects() { return subjects; }
/* Here's where my issues are:
* Override doesn't compile on static methods but how else can I describe that I want to
* override this functionality from the Person class?
*
* Extending 'Person' does not enforce that I need to provide 'name', 'age', etc like it would
* if this was a normal design pattern using the 'new' keyword. I have to manually drag fields
* from 'person' and place them here. This would get VERY messy with an additional class
*
* User can STILL call the Person builder on a Student object, which makes no sense. */
/*@Override*/ public static Builder builder(String name, int age, String school, int year)
{
Student s = new Student(name, age, school, year);
return new Builder(s);
}
public static class Builder extends Person.Builder
{
// Student reference; <--- this should not be needed since we already
// have a variable for this purpose from 'Person.Builder'
public Builder(final Student reference)
{
super(reference);
}
/* Things begins to get very messy here */
public Builder subject(String subject)
{
((Student)reference).subjects.add(subject);
// I guess I could replace the reference with a student one, but
// I feel like that infringes on calling super() builder since we do the work twice.
return this;
}
@Override public Student build()
{
// I can either cast here or
// rewrite 'return reference' every time.
// Seems to infringe a bit on subclassing.
return (Student)super.build();
}
}
}
答案 0 :(得分:1)
你在这里写的是什么:
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("Dormitory")
会返回一个类型为Person.Builder
的变量,因为该方法在Person.Builder
中定义:
public Builder address(String address){
reference.address = address;
return this;
}
并且未在Student.Builder
中覆盖
在编译时,对声明为build()
的变量调用Person.Builder
会返回一个声明为Person
的声明的对象,因为该方法在Person.Builder
中声明为:
public Person build(){
return reference;
}
当然在运行时,返回的对象将是Student
Student.builder("Jack", 19, "NYU", 1)
隐藏了Student
而不是Person
。
为了避免从实现和客户端转换为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个月前回答的另一个问题了 The question and its answers可能会让您感兴趣。
答案 1 :(得分:0)
一些想法作为背景
某些代码
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.
}