如何在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; } }
如何使用java-8工具实现构建器模式?
GenericBuilder
构建可变对象 (后面将讨论不可变对象)的想法是使用方法引用来设置应该构建的实例的setter。 这将我们引向一个通用构建器,它能够使用默认构造函数构建每个POJO – 一个构建器来统治它们;-)
实施是这样的:
public class GenericBuilder { private final Supplier instantiator; private List> instanceModifiers = new ArrayList<>(); public GenericBuilder(Supplier instantiator) { this.instantiator = instantiator; } public static GenericBuilder of(Supplier instantiator) { return new GenericBuilder (instantiator); } public GenericBuilder with(BiConsumer consumer, U value) { Consumer 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; } }
构建器由供应商构建,该供应商创建新实例,然后通过with
方法指定的修改修改这些实例。
GenericBuilder
将像Person
一样用于Person
:
Person value = GenericBuilder.of(Person::new) .with(Person::setName, "Otto").with(Person::setAge, 5).build();
属性和进一步的用法
但是有更多关于那个建筑师的发现。
例如,上面的实现清除了修饰符。 这可以转移到自己的方法中。 因此,构建器将在修改之间保持其状态,并且很容易创建多个相等的实例。 或者,根据instanceModifier
的性质,列出不同的对象。 例如, instanceModifier
可以从增加的计数器中读取其值。
继续这个想法,我们可以实现一个fork
方法,它将返回一个调用它的GenericBuilder
实例的新克隆。 这很容易实现,因为构建器的状态只是instantiator
和instanceModifiers
列表。 从那时起,两个构建器都可以使用其他一些instanceModifiers
进行更改。 它们将共享相同的基础,并在构建的实例上设置一些额外的状态。
在企业应用程序中需要大量实体进行单元甚至集成测试时,我认为最后一点特别有用。 实体不会有神对象,而是建筑师。
GenericBuilder
还可以取代对不同测试值工厂的需求。 在我当前的项目中,有许多工厂用于创建测试实例。 代码紧密耦合到不同的测试场景,并且很难在稍微不同的场景中提取测试工厂的部分以便在另一个测试工厂中重用。 使用GenericBuilder
,重新使用它变得更加容易,因为只有一个特定的instanceModifiers
列表。
要validation创建的实例是否有效,可以使用一组谓词初始化GenericBuilder
,这些谓词在运行所有instanceModifiers
之后在build
方法中进行validation。
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> violated = predicates.stream() .filter(e -> !e.test(value)).collect(Collectors.toList()); if (!violated.isEmpty()) { throw new IllegalStateException(value.toString() + " violates predicates " + violated); } }
不可变对象创建
要使用上述方案创建不可变对象 ,请将不可变对象的状态提取到可变对象中,并使用实例化器和构建器对可变状态对象进行操作。 然后,添加一个函数,为可变状态创建一个新的不可变实例。 但是,这要求不可变对象的状态像这样封装,或者以这种方式改变(基本上将参数对象模式应用于其构造函数)。
这在某种程度上与在java-8之前使用的构建器不同。 在那里,构建器本身是最终创建新实例的可变对象。 现在,我们将构建器保存在可变对象中的状态与构建器function本身分开。
在本质上
停止编写样板构建器模式并使用GenericBuilder
效率。
你可以查看lombok项目
对于你的情况
@Builder public class Person { private String name; private int age; }
它会动态生成代码
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在编译阶段完成它,对开发人员来说是透明的。
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 builderFunction) { builderFunction.accept(this); return this; } public Person createPerson() { return new Person(salutation, firstName, middleName, lastName, suffix, address, isFemale, isEmployed, isHomewOwner); } }
用法
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
免责声明:我是该post的作者
我们可以使用Java 8的Consumerfunction接口来避免多个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 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); } }
请参阅以下链接,了解不同示例的详细信息。
https://dkbalachandar.wordpress.com/2017/08/31/java-8-builder-pattern-with-consumer-interface/
我最近尝试重新访问Java 8中的构建器模式,我目前正在使用以下方法:
public class Person { static public Person create(Consumer 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 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); }
用法:
var person = Person.create( personBuilder -> personBuilder.withName("John Smith").withAge(43) );
优点:
- 干净的构建器界面
- 几乎没有样板代码
- 建造者封装得很好
- 可选属性与目标类的强制属性隔离很容易(可选属性在构建器中指定)
- 目标类中不需要setter(在DDD中,通常不需要setter)
- 使用静态工厂方法创建目标类的实例(而不是使用new关键字,因此可以使用多个静态工厂方法,每个方法都有一个有意义的名称)
可能的缺点:
- 调用代码可以保存对传入构建器的引用,然后搞砸已安装的实例,但是谁会这样做?
- 如果调用代码保存对传入构建器的引用,则可能发生内存泄漏
可能的选择:
我们可以使用构建函数设置构造函数,如下所示:
public class Person { static public Person create(Consumer buildingFunction) { return new Person(buildingFunction); } private String name; private int age; public String getName() { return name; } public int getAge() { return age; } private Person(Consumer 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 } }