- 原文链接 https://dzone.com/articles/design-patterns-the-builder-pattern
开发模式非常有用,它们为一些常见的问题提供了可靠,高效的解决方案。此外,它们成为开发人员之间通用的语言。
Builder模式是一种创建模式 - 换句话说,它用于创建和初始化对象。
问题
假设我们是一个Java团队,为一家银行开发软件。 我们需要一个类来表示银行账户。 以下是BankAccount类的第一个版本(请注意,使用double作为货币的数据类型是一个坏主意 )。
public class BankAccount {
private long accountNumber;
private String owner;
private double balance;
public BankAccount(long accountNumber, String owner, double balance) {
this.accountNumber = accountNumber;
this.owner = owner;
this.balance = balance;
}
//Getters and setters omitted for brevity.
}
非常简单 - 我们可以按如下方式使用它。
BankAccount account = new BankAccount(123L, "Bart", 100.00);
很遗憾,我们要不停添加新功能。 一个新需求是,我们要跟踪每个帐户的利率,开户日期,以及可选的开设分行。 这听起来很容易,所以我们做出了BankAccount类的2.0版本。
public class BankAccount {
private long accountNumber;
private String owner;
private String branch;
private double balance;
private double interestRate;
public BankAccount(long accountNumber, String owner, String branch, double balance, double interestRate) {
this.accountNumber = accountNumber;
this.owner = owner;
this.branch = branch;
this.balance = balance;
this.interestRate = interestRate;
}
//Getters and setters omitted for brevity.
}
于是我们使用以下方式来创建对象
BankAccount account = new BankAccount(456L, "Marge", "Springfield", 100.00, 2.5);
BankAccount anotherAccount = new BankAccount(789L, "Homer", null, 2.5, 100.00); //Oops!
这段代码可以编译通过,但是实际上是错误的,Homer的钱每个月都会增加一倍。 提示:密切关注传递给构造函数的参数的顺序。
如果我们有多个相同类型的连续参数,很容易出现类似的错误, 并且这样的错误很难被发现。 另外,太多的构造函数参数会导致代码变得难以阅读。 如果我们有10个不同的参数,根本无法快速理解每个参数的含义。 更糟糕的是,其中一些值可能是可选的,这意味着我们需要创建多个不同的构造函数来处理所有可能的组合,否则我们必须将null传递给构造函数(丑陋!)。
你可能认为我们可以不写构造函数然后通过setter方法设置参数。 但是,这将带来另外的问题 - 如果开发人员忘记调用必须要调用的setter方法怎么办? 我们最终可能会得到一个只是部分初始化的对象,同样,这样的问题编译阶段无法发现,很难被追踪。
因此,我们需要解决两个问题:
- 构造函数参数太多。
- 对象状态不正确。
这是Builder模式发挥作用的地方。
Builder模式
Builder模式允许我们编写可读,易懂的代码来初始化复杂的对象。你可能已经在Apache Camel或Hamcrest等工具中看到过类似的用法 。
首先我们需要创建一个新类:构建器类,它将包含BankAccount类中所有字段。 我们用构造器类来配置每个参数,然后使用这些参数来返回BankAccount类的对象。 同时,我们将从BankAccount类中删除公共构造函数,并将其替换为私有构造函数,以便只能通过构建器创建BankAccount对象。
如下:
public class BankAccount {
public static class Builder {
private long accountNumber; //This is important, so we'll pass it to the constructor.
private String owner;
private String branch;
private double balance;
private double interestRate;
public Builder(long accountNumber) {
this.accountNumber = accountNumber;
}
public Builder withOwner(String owner){
this.owner = owner;
return this; //By returning the builder each time, we can create a fluent interface.
}
public Builder atBranch(String branch){
this.branch = branch;
return this;
}
public Builder openingBalance(double balance){
this.balance = balance;
return this;
}
public Builder atRate(double interestRate){
this.interestRate = interestRate;
return this;
}
public BankAccount build(){
//Here we create the actual bank account object, which is always in a fully initialised state when it's returned.
BankAccount account = new BankAccount(); //Since the builder is in the BankAccount class, we can invoke its private constructor.
account.accountNumber = this.accountNumber;
account.owner = this.owner;
account.branch = this.branch;
account.balance = this.balance;
account.interestRate = this.interestRate;
return account;
}
}
//Fields omitted for brevity.
private BankAccount() {
//Constructor is now private.
}
//Getters and setters omitted for brevity.
}
我们现在可以创建新帐户,如下所示。
BankAccount account = new BankAccount.Builder(1234L)
.withOwner("Marge")
.atBranch("Springfield")
.openingBalance(100)
.atRate(2.5)
.build();
BankAccount anotherAccount = new BankAccount.Builder(4567L)
.withOwner("Homer")
.atBranch("Springfield")
.openingBalance(100)
.atRate(2.5)
.build();
这样的代码大大提高了可读性。
总结
我们通过一个示例开始,代码从简单开始,然后变得越来越复杂。 然后,我们使用Builder模式来解决我们发现的问题。