如何在 Java 8 中实现构建器模式?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/31754786/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to implement the builder pattern in Java 8?
提问by SpaceTrucker
Often I find it tedious to implement the builder pattern with pre-java-8 setups. There is always lots of nearly duplicated code. The builder itself could be considered boilerplate.
我经常发现使用 java-8 之前的设置来实现构建器模式很乏味。总是有很多几乎重复的代码。构建器本身可以被视为样板。
In fact there are code duplicate detectors, that would consider nearly each method of a builder made with pre-java-8 facilities as a duplicate of every other method.
事实上,有代码重复检测器,它会将使用 java-8 之前的工具制作的构建器的几乎每种方法都视为其他所有方法的重复。
So considering the following class and it's pre-java-8 builder:
因此,考虑以下类,它是 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;
}
}
How should the builder pattern be implemented using java-8 facilities?
构建器模式应该如何使用 java-8 工具来实现?
采纳答案by SpaceTrucker
The GenericBuilder
这 GenericBuilder
The idea for building mutable objects(immutable objects are addressed later on) is to use method references to setters of the instance that should be built. This leads us to a generic builder that is capable of building every POJO with a default constructor - one builder to rule them all ;-)
构建可变对象(不可变对象将在后面讨论)的想法是使用对应该构建的实例的 setter 的方法引用。这将我们引向一个通用构建器,该构建器能够使用默认构造函数构建每个 POJO - 一个构建器来统治它们;-)
The implementation is this:
实现是这样的:
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;
}
}
The builder is constructed with a supplier that creates new instances and then those instances are modified by the modifications specified with the with
method.
构建器由创建新实例的供应商构建,然后这些实例通过with
方法指定的修改进行修改。
The GenericBuilder
would be used for Person
like this:
在GenericBuilder
将用于Person
如下:
Person value = GenericBuilder.of(Person::new)
.with(Person::setName, "Otto").with(Person::setAge, 5).build();
Properties and further Usages
属性和进一步用途
But there is more about that builder to discover.
但是还有更多关于该构建器的内容有待发现。
For example, the above implementation clears the modifiers. This could be moved into its own method. Therefore, the builder would keep its state between modifications and it would be easy create multiple equal instances. Or, depending on the nature of an instanceModifier
, a list of varying objects. For example, an instanceModifier
could read its value from an increasing counter.
例如,上面的实现清除了修饰符。这可以移动到它自己的方法中。因此,构建器将在修改之间保持其状态,并且很容易创建多个相等的实例。或者,根据 的性质instanceModifier
,不同对象的列表。例如,instanceModifier
可以从递增的计数器中读取其值。
Continuing with this thought, we could implement a fork
method that would return a new clone of the GenericBuilder
instance that it is called on. This is easily possible because the state of the builder is just the instantiator
and the list of instanceModifiers
. From there on, both builders could be altered with some other instanceModifiers
. They would share the same basis and have some additional state set on built instances.
继续这个想法,我们可以实现一个fork
方法,该方法将返回GenericBuilder
调用它的实例的新克隆。这很容易实现,因为构建器的状态只是instantiator
和 的列表instanceModifiers
。从那时起,两个构建器都可以与其他instanceModifiers
. 它们将共享相同的基础,并在构建的实例上设置一些额外的状态。
The last point I consider especially helpful when needing heavy entities for unit or even integration tests in enterprise applications. There would be no god-object for entities, but for builders instead.
在企业应用程序中需要大量实体进行单元甚至集成测试时,我认为最后一点特别有用。实体没有上帝对象,而是建造者。
The GenericBuilder
can also replace the need for different test value factories. In my current project, there are many factories used for creating test instances. The code is tightly coupled to different test scenarios and it is difficult to extract portions of a test factory for reuse in another test factory in a slightly different scenario. With the GenericBuilder
, reusing this becomes much easier as there is only a specific list of instanceModifiers
.
该GenericBuilder
还可以取代不同的测试值工厂的需要。在我目前的项目中,有很多工厂用于创建测试实例。代码与不同的测试场景紧密耦合,并且很难在稍微不同的场景中提取测试工厂的部分以在另一个测试工厂中重用。使用GenericBuilder
,重用它变得更加容易,因为只有一个特定的instanceModifiers
.
To verify that created instances are valid, the GenericBuilder
could be initialized with a set of predicates, which are verified in the build
method after all instanceModifiers
are run.
为了验证创建的实例是否有效,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);
}
}
Immutable object creation
不可变对象创建
To use the above scheme for the creation of immutable objects, extract the state of the immutable object into a mutable object and use the instantiator and builder to operate on the mutable state object. Then, add a function that will create a new immutable instance for the mutable state. However, this requires that the immutable object either has its state encapsulated like this or it be changed in that fashion (basically applying parameter object pattern to its constructor).
要使用上述创建不可变对象的方案,将不可变对象的状态提取为可变对象,并使用实例化器和构建器对可变状态对象进行操作。然后,添加一个函数,该函数将为可变状态创建一个新的不可变实例。然而,这要求不可变对象要么像这样封装它的状态,要么以这种方式改变它(基本上将参数对象模式应用于其构造函数)。
This is in some way different than a builder was used in pre-java-8 times. There, the builder itself was the mutable object that created a new instance at the end. Now, we have a separation of the state a builder keeps in a mutable object and the builder functionality itself.
这在某种程度上与 java-8 之前使用的构建器不同。在那里,构建器本身是最后创建新实例的可变对象。现在,我们将构建器在可变对象中保持的状态与构建器功能本身分离。
In essence
Stop writing boilerplate builder patterns and get productive using the GenericBuilder
.
本质上
停止编写样板构建器模式并使用GenericBuilder
.
回答by popcorny
You can check the lombok project
你可以查看lombok项目
For your case
对于您的情况
@Builder
public class Person {
private String name;
private int age;
}
It would generate the code on the fly
它会即时生成代码
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(){...}
}
}
Lombok do it on the compilation phase and is transparent for developers.
Lombok 在编译阶段进行,并且对开发人员是透明的。
回答by Balachandar
We can use Consumer functional interface of Java 8 to avoid multiple getter/setter methods.
我们可以使用 Java 8 的 Consumer 函数接口来避免多个 getter/setter 方法。
Refer the below-updated code with Consumer interface.
请参阅下面更新的带有 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);
}
}
Refer the below link to know the detailed information with the different examples.
请参阅以下链接以了解不同示例的详细信息。
https://dkbalachandar.wordpress.com/2017/08/31/java-8-builder-pattern-with-consumer-interface/
https://dkbalachandar.wordpress.com/2017/08/31/java-8-builder-pattern-with-consumer-interface/
回答by Sujit Kamthe
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);
}
}
Usage
用法
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();
参考:https: //medium.com/beingprofessional/think-functional-advanced-builder-pattern-using-lambda-284714b85ed5
Disclaimer: I am the author of the post
免责声明:我是帖子的作者
回答by Stéphane Appercel
I have recently tried to revisit the builder pattern in Java 8, and I am currently using the following approach:
我最近尝试重新审视 Java 8 中的构建器模式,目前我正在使用以下方法:
public class Person {
static public Person create(Consumer<PersonBuilder> buildingFunction) {
return new Person().build(buildingFunction);
}
private String name;
private int age;
public String getName() {
return name;
}
public int getAge() {
return age;
}
private Person() {
}
private Person build(Consumer<PersonBuilder> buildingFunction) {
buildingFunction.accept(new PersonBuilder() {
@Override
public PersonBuilder withName(String name) {
Person.this.name = name;
return this;
}
@Override
public PersonBuilder withAge(int age) {
Person.this.age = age;
return this;
}
});
if (name == null || name.isEmpty()) {
throw new IllegalStateException("the name must not be null or empty");
}
if (age <= 0) {
throw new IllegalStateException("the age must be > 0");
}
// check other invariants
return this;
}
}
public interface PersonBuilder {
PersonBuilder withName(String name);
PersonBuilder withAge(int age);
}
Usage:
用法:
var person = Person.create(
personBuilder -> personBuilder.withName("John Smith").withAge(43)
);
Advantages:
好处:
- A clean builder interface
- Little to no boilerplate code
- The builder is well encapsulated
- It's easy to segregate the optional attributes from the mandatory attributes of the target class (the optional attributes are specified in the builder)
- No setter needed in the target class (in DDD, you generally don't want setters)
- Use of a static factory method to create an instance of the target class (instead of using the new keyword, so it's possible to have several static factory methods, each with a meaningful name)
- 一个干净的构建器界面
- 几乎没有样板代码
- builder 封装得很好
- 很容易将可选属性与目标类的必需属性分开(可选属性在构建器中指定)
- 目标类中不需要 setter(在 DDD 中,您通常不需要 setter)
- 使用静态工厂方法创建目标类的实例(而不是使用 new 关键字,因此可以有多个静态工厂方法,每个都有一个有意义的名称)
Possible drawbacks:
可能的缺点:
- The calling code can save a reference to the passed-in builder and later screw up the mounted instance, but who will do that?
- If the calling code saves a reference to the passed-in builder, a memory leak can occur
- 调用代码可以保存对传入构建器的引用,然后再搞砸已挂载的实例,但谁会这样做呢?
- 如果调用代码保存了对传入构建器的引用,则可能发生内存泄漏
Possible alternative:
可能的替代方案:
We can setup a constructor with a building function, as follows:
我们可以设置一个带有构建函数的构造函数,如下所示:
public class Person {
static public Person create(Consumer<PersonBuilder> buildingFunction) {
return new Person(buildingFunction);
}
private String name;
private int age;
public String getName() {
return name;
}
public int getAge() {
return age;
}
private Person(Consumer<PersonBuilder> buildingFunction) {
buildingFunction.accept(new PersonBuilder() {
@Override
public PersonBuilder withName(String name) {
Person.this.name = name;
return this;
}
@Override
public PersonBuilder withAge(int age) {
Person.this.age = age;
return this;
}
});
if (name == null || name.isEmpty()) {
throw new IllegalStateException("the name must not be null or empty");
}
if (age <= 0) {
throw new IllegalStateException("the age must be > 0");
}
// check other invariants
}
}