设计模式:Builder模式(译)

  • 原文链接 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模式来解决我们发现的问题。

你可能感兴趣的:(设计模式:Builder模式(译))