Spa*_*ker 19 java builder java-8
我常常发现使用pre-java-8设置实现构建器模式很繁琐.总是有很多几乎重复的代码.构建器本身可以被视为样板.
事实上,有代码重复检测器,几乎可以考虑使用pre-java-8工具制作的构建器的每个方法作为每个其他方法的副本.
所以考虑下面的类和它的pre-java-8构建器:
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class PersonBuilder {
private static class PersonState {
public String name;
public int age;
}
private PersonState state = new PersonState();
public PersonBuilder withName(String name) {
state.name = name;
return this;
}
public PersonBuilder withAge(int age) {
state.age = age;
return this;
}
public Person build() {
Person person = new Person();
person.setAge(state.age);
person.setName(state.name);
state = new PersonState();
return person;
}
}
Run Code Online (Sandbox Code Playgroud)
如何使用java-8工具实现构建器模式?
Spa*_*ker 73
GenericBuilder构建可变对象(后面将讨论不可变对象)的想法是使用方法引用来设置应该构建的实例的setter.这导致我们使用一个通用构建器,它能够使用默认构造函数构建每个POJO - 一个构建器来统治它们;-)
实施是这样的:
public class GenericBuilder<T> {
private final Supplier<T> instantiator;
private List<Consumer<T>> instanceModifiers = new ArrayList<>();
public GenericBuilder(Supplier<T> instantiator) {
this.instantiator = instantiator;
}
public static <T> GenericBuilder<T> of(Supplier<T> instantiator) {
return new GenericBuilder<T>(instantiator);
}
public <U> GenericBuilder<T> with(BiConsumer<T, U> consumer, U value) {
Consumer<T> c = instance -> consumer.accept(instance, value);
instanceModifiers.add(c);
return this;
}
public T build() {
T value = instantiator.get();
instanceModifiers.forEach(modifier -> modifier.accept(value));
instanceModifiers.clear();
return value;
}
}
Run Code Online (Sandbox Code Playgroud)
构建器由供应商构建,该供应商创建新实例,然后通过with方法指定的修改修改这些实例.
在GenericBuilder将用于Person如下:
Person value = GenericBuilder.of(Person::new)
.with(Person::setName, "Otto").with(Person::setAge, 5).build();
Run Code Online (Sandbox Code Playgroud)
但是有更多关于那个建设者的发现.
例如,上面的实现清除了修饰符.这可以转移到自己的方法中.因此,构建器将在修改之间保持其状态,并且很容易创建多个相等的实例.或者,根据a的性质instanceModifier,列出不同的对象.例如,a instanceModifier可以从增加的计数器中读取其值.
继续这个想法,我们可以实现一个fork方法,它将返回一个GenericBuilder被调用的实例的新克隆.这很容易实现,因为构建器的状态只是instantiator和列表instanceModifiers.从那以后,两个建设者都可以改变其他一些instanceModifiers.它们将共享相同的基础,并在构建的实例上设置一些额外的状态.
在企业应用程序中需要大量实体进行单元甚至集成测试时,我认为最后一点特别有用.对于实体而言,没有上帝对象,而是对于建造者而言.
这GenericBuilder也可以取代对不同测试值工厂的需求.在我当前的项目中,有许多工厂用于创建测试实例.代码紧密耦合到不同的测试场景,并且很难在稍微不同的场景中提取测试工厂的部分以便在另一个测试工厂中重用.有了这个GenericBuilder,重新使用它变得更容易,因为只有一个特定的列表instanceModifiers.
要验证创建的实例是否有效,GenericBuilder可以使用一组谓词对其进行初始化,这些谓词在运行build完毕后在方法中进行验证instanceModifiers.
public T build() {
T value = instantiator.get();
instanceModifiers.forEach(modifier -> modifier.accept(value));
verifyPredicates(value);
instanceModifiers.clear();
return value;
}
private void verifyPredicates(T value) {
List<Predicate<T>> violated = predicates.stream()
.filter(e -> !e.test(value)).collect(Collectors.toList());
if (!violated.isEmpty()) {
throw new IllegalStateException(value.toString()
+ " violates predicates " + violated);
}
}
Run Code Online (Sandbox Code Playgroud)
要使用上述方案创建不可变对象,请将不可变对象的状态提取到可变对象中,并使用实例化器和构建器对可变状态对象进行操作.然后,添加一个函数,为可变状态创建一个新的不可变实例.但是,这要求不可变对象的状态像这样封装,或者以这种方式改变(基本上将参数对象模式应用于其构造函数).
这在某种程度上不同于在java-8之前使用的构建器.在那里,构建器本身是最终创建新实例的可变对象.现在,我们将构建器保存在可变对象中的状态与构建器功能本身分开.
本质上
停止编写样板构建器模式并使用GenericBuilder.
你可以查看lombok项目
对于你的情况
@Builder
public class Person {
private String name;
private int age;
}
Run Code Online (Sandbox Code Playgroud)
它会动态生成代码
public class Person {
private String name;
private int age;
public String getName(){...}
public void setName(String name){...}
public int getAge(){...}
public void setAge(int age){...}
public Person.Builder builder() {...}
public static class Builder {
public Builder withName(String name){...}
public Builder withAge(int age){...}
public Person build(){...}
}
}
Run Code Online (Sandbox Code Playgroud)
Lombok在编译阶段完成它,对开发人员来说是透明的.
public class PersonBuilder {
public String salutation;
public String firstName;
public String middleName;
public String lastName;
public String suffix;
public Address address;
public boolean isFemale;
public boolean isEmployed;
public boolean isHomewOwner;
public PersonBuilder with(
Consumer<PersonBuilder> builderFunction) {
builderFunction.accept(this);
return this;
}
public Person createPerson() {
return new Person(salutation, firstName, middleName,
lastName, suffix, address, isFemale,
isEmployed, isHomewOwner);
}
}
Run Code Online (Sandbox Code Playgroud)
用法
Person person = new PersonBuilder()
.with($ -> {
$.salutation = "Mr.";
$.firstName = "John";
$.lastName = "Doe";
$.isFemale = false;
})
.with($ -> $.isHomewOwner = true)
.with($ -> {
$.address =
new PersonBuilder.AddressBuilder()
.with($_address -> {
$_address.city = "Pune";
$_address.state = "MH";
$_address.pin = "411001";
}).createAddress();
})
.createPerson();
Run Code Online (Sandbox Code Playgroud)
参考:https://medium.com/beingprofessional/think-functional-advanced-builder-pattern-using-lambda-284714b85ed5
免责声明:我是该帖子的作者
小智 5
我们可以使用 Java 8 的 Consumer 函数接口来避免多个 getter/setter 方法。
请参阅下面更新的带有 Consumer 接口的代码。
import java.util.function.Consumer;
public class Person {
private String name;
private int age;
public Person(Builder Builder) {
this.name = Builder.name;
this.age = Builder.age;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Person{");
sb.append("name='").append(name).append('\'');
sb.append(", age=").append(age);
sb.append('}');
return sb.toString();
}
public static class Builder {
public String name;
public int age;
public Builder with(Consumer<Builder> function) {
function.accept(this);
return this;
}
public Person build() {
return new Person(this);
}
}
public static void main(String[] args) {
Person user = new Person.Builder().with(userData -> {
userData.name = "test";
userData.age = 77;
}).build();
System.out.println(user);
}
}
Run Code Online (Sandbox Code Playgroud)
请参阅以下链接以了解不同示例的详细信息。
https://dkbalachandar.wordpress.com/2017/08/31/java-8-builder-pattern-with-consumer-interface/
| 归档时间: |
|
| 查看次数: |
26855 次 |
| 最近记录: |