设计模式:构建器模式


我一直想写一系列有关设计模式的文章已有很长时间了。模式是开发人员工具箱中非常有价值的组件–它们解决了已被接受的有效解决方案的常见问题。此外,它们有助于开发人员之间共享词汇表。

本系列假定您对面向对象编程(OOP)有所了解。但是,我将尽力使示例尽可能简单和易于访问,与晦涩的示例相比,更倾向于实际的实现。如果您正在寻找有关模式的权威性学术文章,这就是您想要的: 设计模式:可重用的面向对象软件的元素。

我们将从构建器模式(我的最爱之一)开始。Builder模式是一种创建模式–换句话说,它用于创建和配置对象。我特别喜欢Joshua Bloch在Effective Java中使用的示例 。

问题

在此示例中,我们假设我们是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个不同的参数,一眼就很难识别出构造函数中的内容。更糟的是,其中一些值可能是可选的,这意味着我们将需要创建一堆重载的构造函数来处理所有可能的组合,否则我们将必须将null传递给构造函数(丑陋!)。

您可能会认为我们可以通过调用无参数构造函数来解决此问题,然后通过setter方法设置帐户。但是,这使我们面临另一个问题–如果开发人员忘记调用特定的setter方法会怎样?我们可能会得到一个仅被部分初始化的对象,并且再次,编译器不会看到任何问题。

因此,我们需要解决两个特定的问题:

  • 构造函数参数过多。
  • 对象状态不正确。 这是构建器模式起作用的地方。

模式

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(); ``` ​ 这段代码更冗长吗?是的。比较清楚吗?是的。好点吗?由于我们的大部分时间都花在阅读代码而不是编写代码上,所以我可以肯定的是,是的。

概括

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

如果您发现自己一直在向构造函数添加新参数,从而导致代码容易出错且难以阅读,那么现在是退后一步并考虑重构代码以使用Builder的好时机。


原文链接:https://blog.csdn.net/Beibnjin/article/details/113827586?utm_medium=distribute.pc_category.none-task-blog-hot-1.nonecase&depth_1-utm_source=distribute.pc_category.none-task-blog-hot-1.nonecase&request_id=