转发: The builder&nbs…

本文探讨了在Java中如何使用建造者模式解决构造带有大量属性且部分为可选参数的对象的问题。通过实例展示了建造者模式的优势,包括提高代码可读性、维护性和确保对象的一致性状态。
这篇文章真的很好, 让你如何知道怎么把一个很理论化的builder pattern 用到实际中来。 故分享之

I’m not going to dive into much details about the pattern because there’s already tons of posts and books that explain it in fine detail. Instead, I’m going to tell you why and when you should consider using it. However, it is worth mentioning that this pattern is a bit different to the one presented in the Gang of Four book. While the original pattern focuses on abstracting the steps of construction so that by varying the builder implementation used we can get a different result, the pattern explained in this post deals with removing the unnecessary complexity that stems from multiple constructors, multiple optional parameters and overuse of setters.

Imagine you have a class with a substantial amount of attributes like the User class below. Let’s assume that you want to make the class immutable (which, by the way,
 
unless there’s a really good reason not to you should always strive to do. But we’ll get to that in a different post).

1public class User {
2    private final String firstName;    //required
3    private final String lastName;    //required
4    private final int age;    //optional
5    private final String phone;    //optional
6    private final String address;    //optional
7...
8}

Now, imagine that some of the attributes in your class are required while others are optional. How would you go about building an object of this class? All attributes are declared final so you have to set them all in the constructor, but you also want to give the clients of this class the chance of ignoring the optional attributes.

A first and valid option would be to have a constructor that only takes the required attributes as parameters, one that takes all the required attributes plus the first optional one, another one that takes two optional attributes and so on. What does that look like? Something like this:

01public User(String firstName, String lastName) {
02    this(firstName, lastName, 0);
03}
04 
05public User(String firstName, String lastName, int age) {
06    this(firstName, lastName, age, '');
07}
08 
09public User(String firstName, String lastName, int age, String phone) {
10    this(firstName, lastName, age, phone, '');
11}
12 
13public User(String firstName, String lastName, int age, String phone, String address) {
14    this.firstName = firstName;
15    this.lastName = lastName;
16    this.age = age;
17    this.phone = phone;
18    this.address = address;
19}

The good thing about this way of building objects of the class is that it works. However, the problem with this approach should be pretty obvious. When you only have a couple of attributes is not such a big deal, but as that number increases the code becomes harder to read and maintain. More importantly, the code becomes increasingly harder for clients. Which constructor should I invoke as a client? The one with 2 parameters? The one with 3? What is the default value for those parameters where I don’t pass an explicit value? What if I want to set a value for address but not for age and phone? In that case I would have to call the constructor that takes all the parameters and pass default values for those that I don’t care about. Additionally, several parameters with the same type can be confusing. Was the first String the phone number or the address?

So what other choice do we have for these cases? We can always follow the JavaBeans convention, where we have a default no-arg constructor and have setters and getters for every attribute. Something like:

01public class User {
02    private String firstName; // required
03    private String lastName; // required
04    private int age; // optional
05    private String phone; // optional
06    private String address;  //optional
07 
08    public String getFirstName() {
09        return firstName;
10    }
11    public void setFirstName(String firstName) {
12        this.firstName = firstName;
13    }
14    public String getLastName() {
15        return lastName;
16    }
17    public void setLastName(String lastName) {
18        this.lastName = lastName;
19    }
20    public int getAge() {
21        return age;
22    }
23    public void setAge(int age) {
24        this.age = age;
25    }
26    public String getPhone() {
27        return phone;
28    }
29    public void setPhone(String phone) {
30        this.phone = phone;
31    }
32    public String getAddress() {
33        return address;
34    }
35    public void setAddress(String address) {
36        this.address = address;
37    }
38}

This approach seems easier to read and maintain. As a client I can just create an empty object and then set only the attributes that I’m interested in. So what’s wrong with it? There are two main problems with this solution. The first issue has to do with having an instance of this class in an inconsistent state. If you want to create an User object with values for all its 5 attributes then the object will not have a complete state until all the setX methods have been invoked. This means that some part of the client application might see this object and assume that is already constructed while that’s actually not the case. The second disadvantage of this approach is that now the User class is mutable. You’re loosing all the benefits of immutable objects.

Fortunately there is a third choice for these cases, the builder pattern. The solution will look something like the following.

01public class User {
02    private final String firstName; // required
03    private final String lastName; // required
04    private final int age; // optional
05    private final String phone; // optional
06    private final String address; // optional
07 
08    private User(UserBuilder builder) {
09        this.firstName = builder.firstName;
10        this.lastName = builder.lastName;
11        this.age = builder.age;
12        this.phone = builder.phone;
13        this.address = builder.address;
14    }
15 
16    public String getFirstName() {
17        return firstName;
18    }
19 
20    public String getLastName() {
21        return lastName;
22    }
23 
24    public int getAge() {
25        return age;
26    }
27 
28    public String getPhone() {
29        return phone;
30    }
31 
32    public String getAddress() {
33        return address;
34    }
35 
36    public static class UserBuilder {
37        private final String firstName;
38        private final String lastName;
39        private int age;
40        private String phone;
41        private String address;
42 
43        public UserBuilder(String firstName, String lastName) {
44            this.firstName = firstName;
45            this.lastName = lastName;
46        }
47 
48        public UserBuilder age(int age) {
49            this.age = age;
50            return this;
51        }
52 
53        public UserBuilder phone(String phone) {
54            this.phone = phone;
55            return this;
56        }
57 
58        public UserBuilder address(String address) {
59            this.address = address;
60            return this;
61        }
62 
63        public User build() {
64            return new User(this);
65        }
66 
67    }
68}

A couple of important points worth noting:

  • The User constructor is private, which means that this class can not be directly instantiated from the client code.
  • The class is once again immutable. All attributes are final and they’re set on the constructor. Additionally, we only provide getters for them.
  • The builder uses the Fluent Interface idiom to make the client code more readable (we’ll see an example of this in a moment).
  • The builder constructor only receives the required attributes and this attributes are the only ones that are defined “final” on the builder to ensure that their values are set on the constructor.

The use of the builder pattern has all the advantages of the first two approaches I mentioned at the beginning and none of their shortcomings. The client code is easier to write and, more importantly, to read. The only critique that I’ve heard about the pattern is the fact that you have to duplicate the class’ attributes on the builder. However, given the fact that the builder class is usually a static member class of the class it builds, they can evolve together fairly easy.

Now, how does the client code trying to create a new User object looks like? Let’s see:

1public User getUser() {
2    return new
3            User.UserBuilder('Jhon', 'Doe')
4            .age(30)
5            .phone('1234567')
6            .address('Fake address 1234')
7            .build();
8}

Pretty neat, isn’t it? You can build a User object in 1 line of code and, most importantly, is very easy to read. Moreover, you’re making sure that whenever you get an object of this class is not going to be on an incomplete state.

This pattern is really flexible. A single builder can be used to create multiple objects by varying the builder attributes between calls to the “build” method. The builder could even auto-complete some generated field between each invocation, such as an id or serial number.

An important point is that, like a constructor, a builder can impose invariants on its parameters. The build method can check these invariants and throw an IllegalStateException if they are not valid.
It is critical that they be checked after copying the parameters from the builder to the object, and that they be checked on the object fields rather than the builder fields. The reason for this is that, since the builder is not thread-safe, if we check the parameters before actually creating the object their values can be changed by another thread between the time the parameters are checked and the time they are copied. This period of time is known as the “window of vulnerability”. In our User example this could look like the following:

1public User build() {
2    User user = new user(this);
3    if (user.getAge() 120) {
4        throw new IllegalStateException(“Age out of range”); // thread-safe
5    }
6    return user;
7}

The previous version is thread-safe because we first create the user and then we check the invariants on the immutable object. The following code looks functionally identical but it’s not thread-safe and you should avoid doing things like this:

1public User build() {
2    if (age 120) {
3        throw new IllegalStateException(“Age out of range”); // bad, not thread-safe
4    }
5    // This is the window of opportunity for a second thread to modify the value of age
6    return new User(this);
7}

A final advantage of this pattern is that a builder could be passed to a method to enable this method to create one or more objects for the client, without the method needing to know any kind of details about how the objects are created. In order to do this you would usually have a simple interface like:

1public interface Builder {
2    T build();
3}

In the previous User example, the UserBuilder class could implement Builder. Then, we could have something like:

1UserCollection buildUserCollection(Builder
(Kriging_NSGA2)克里金模型结合多目标遗传算法求最优因变量及对应的最佳自变量组合研究(Matlab代码实现)内容概要:本文介绍了克里金模型(Kriging)与多目标遗传算法NSGA-II相结合的方法,用于求解最优因变量及其对应的最佳自变量组合,并提供了完整的Matlab代码实现。该方法首先利用克里金模型构建高精度的代理模型,逼近复杂的非线性系统响应,减少计算成本;随后结合NSGA-II算法进行多目标优化,搜索帕累托前沿解集,从而获得多个最优折衷方案。文中详细阐述了代理模型构建、算法集成流程及参数设置,适用于工程设计、参数反演等复杂优化问题。此外,文档还展示了该方法在SCI一区论文中的复现应用,体现了其科学性与实用性。; 适合人群:具备一定Matlab编程基础,熟悉优化算法和数值建模的研究生、科研人员及工程技术人员,尤其适合从事仿真优化、实验设计、代理模型研究的相关领域工作者。; 使用场景及目标:①解决高计算成本的多目标优化问题,通过代理模型降低仿真次数;②在无法解析求导或函数高度非线性的情况下寻找最优变量组合;③复现SCI高水平论文中的优化方法,提升科研可信度与效率;④应用于工程设计、能源系统调度、智能制造等需参数优化的实际场景。; 阅读建议:建议读者结合提供的Matlab代码逐段理解算法实现过程,重点关注克里金模型的构建步骤与NSGA-II的集成方式,建议自行调整测试函数或实际案例验证算法性能,并配合YALMIP等工具包扩展优化求解能力。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值