设计模式:建造者模式

 点击“终码一生”,关注,置顶公众号

每日技术干货,第一时间送达!

模式是开发人员工具箱中非常有价值的组件——它们解决了已被接受的有效解决方案的常见问题。此外,它们有助于开发人员之间共享词汇。

本系列假设您了解面向对象编程 (OOP)。然而,我会尽量让这些示例尽可能简单易懂,优先考虑实用的实现而不是晦涩的示例。如果您正在寻找有关模式的权威学术文本,这就是您想要的:设计模式:可重用的面向对象软件的元素。

我们将从 Builder 模式(我最喜欢的模式之一)开始。Builder 模式是一种创建模式——换句话说,它用于创建和配置对象。

1、问题

对于此示例,我们将假设我们是 Java 团队的一员,该团队正在为一家银行开发一款软件。除其他外,我们需要一种表示银行账户的方法。我们的第一遍看起来像这样(请注意,对实际货币值使用 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!

我们的编译器,应该是我们的安全网,认为这段代码没问题。然而,实际的含义是荷马的钱每个月都会翻一番。(如果有人知道有这样的回报的帐户,请告诉我!)你能找出原因吗?

提示:密切注意传递给构造函数的参数的顺序。

如果我们有多个相同类型的连续参数,很容易意外地交换它们。由于编译器不会将其视为错误,因此它可能会在运行时的某个地方表现为问题——这可能会变成一个棘手的调试练习。此外,添加更多的构造函数参数会导致代码变得更难阅读。如果我们有 10 个不同的参数,那么一目了然地识别构造函数中的内容将变得非常困难。更糟糕的是,其中一些值可能是可选的,这意味着我们需要创建一堆重载的构造函数来处理所有可能的组合,或者我们必须将空值传递给我们的构造函数(丑陋!)。

您可能会认为我们可以通过调用无参数构造函数然后通过 setter 方法设置帐户来缓解此问题。然而,这让我们面临另一个问题——如果开发人员忘记调用特定的 setter 方法会发生什么?我们最终可能会得到一个仅部分初始化的对象,而且编译器也不会发现它有任何问题。

因此,我们需要解决两个具体问题:

  • 构造函数参数太多。

  • 对象状态不正确。

这就是建造者模式发挥作用的地方。

2、模式

Builder 模式允许我们编写可读、可理解的代码来设置复杂的对象。它通常使用 流畅的界面实现,您可能已经在 Apache Camel 或 Hamcrest等工具中看到过这种界面。该构建器将包含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();

这段代码更冗长吗?是的。是不是更清楚了?是的。好点吗?由于我们大部分时间都花在阅读代码而不是编写代码上,所以我很确定是的,是的。

3、概括

我们研究了一个示例,其中代码开始很简单,然后变得复杂。然后,我们使用 Builder 模式来解决我们发现的问题。

如果您发现自己处于不断向构造函数添加新参数的情况,导致代码容易出错且难以阅读,也许现在是退一步考虑重构代码以使用 Builder 的好时机。

PS:防止找不到本篇文章,可以收藏点赞,方便翻阅查找哦

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

终码一生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值