Contents

设计模式简介

1. 简介

在软件工程中,设计模式描述了软件设计中最常遇到的问题的既定解决方案。它代表了经验丰富的软件开发人员经过长期反复试验而形成的最佳实践。

设计模式在 1994 年由 Erich Gamma、John Vlissides、Ralph Johnson 和 Richard Helm(也称为 Gang of Four 或 GoF)一书《设计模式:可重用的面向对象软件的元素》出版后广受欢迎。

在本文中,我们将探讨创建设计模式及其类型。我们还将查看一些代码示例并讨论这些模式适合我们设计的情况。

2. 设计模式

**创建型设计模式关注对象的创建方式。**它们通过以受控方式创建对象来降低复杂性和不稳定性。

new运算符通常被认为是有害的,因为它会将对象分散到整个应用程序中。随着时间的推移,由于类变得紧密耦合,因此更改实现变得具有挑战性。

创建设计模式通过将客户端与实际初始化过程完全分离来解决这个问题。

在本文中,我们将讨论四种创建设计模式:

  1. Singleton —— 确保在整个应用程序中最多只存在一个对象实例
  2. Factory —— 创建几个相关类的对象,而不指定要创建的确切对象
  3. Abstract Factory —— 创建相关依赖对象的族
  4. Builder —— 使用逐步方法构建复杂对象

现在让我们详细讨论这些模式中的每一个。

3. 单例设计模式

单例设计模式旨在通过确保整个 Java 虚拟机中仅存在一个对象实例来检查特定类的对象的初始化。

Singleton 类还为对象提供了一个唯一的全局访问点,以便对访问点的每个后续调用都只返回该特定对象。

3.1.单例模式示例

尽管 GoF 引入了 Singleton 模式,但众所周知,原始实现在多线程场景中存在问题。

所以在这里,我们将遵循一种更优化的方法,即使用静态内部类:

public class Singleton  {
    private Singleton() {}

    private static class SingletonHolder {
        public static final Singleton instance = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

在这里,我们创建了一个包含Singleton类实例的static内部类。它仅在有人调用*getInstance()*方法而不是在加载外部类时创建实例。

对于 Singleton 类,这是一种广泛使用的方法,因为它不需要同步、线程安全、强制执行延迟初始化并且样板代码相对较少。

另外,请注意构造函数具有private访问修饰符。这是创建 Singleton 的要求,因为public构造函数意味着任何人都可以访问它并开始创建新实例。

请记住,这不是最初的 GoF 实现。对于原始版本,请访问此文章 关于 Java 中的 Singletons。

3.2. 何时使用单例设计模式

  • 对于创建成本高的资源(如数据库连接对象)
  • 将所有记录器保持为单例是一种很好的做法,这可以提高性能
  • 提供对应用程序配置设置的访问的类
  • 包含以共享模式访问的资源的类

4. 工厂方法设计模式

工厂设计模式或工厂方法设计模式是 Java 中最常用的设计模式之一。

根据 GoF 的说法,这种模式**“定义了一个用于创建对象的接口,但让子类决定实例化哪个类。**Factory 方法允许类将实例化推迟到子类”。

该模式通过创建一种虚拟构造函数将初始化类的责任从客户端委托给特定的工厂类。

为了实现这一点,我们依赖于为我们提供对象的工厂,隐藏了实际的实现细节。使用通用接口访问创建的对象。

4.1. 工厂方法设计模式示例

在本例中,我们将创建一个Polygon接口,该接口将由几个具体类实现。PolygonFactory将用于从该系列中获取对象:

/uploads/creational_design_patterns/1.png

让我们首先创建Polygon接口:

public interface Polygon {
    String getType();
}

接下来,我们将创建一些实现,例如SquareTriangle等,它们实现了这个接口并返回一个Polygon类型的对象。

现在我们可以创建一个工厂,将边数作为参数并返回此接口的适当实现:

public class PolygonFactory {
    public Polygon getPolygon(int numberOfSides) {
        if(numberOfSides == 3) {
            return new Triangle();
        }
        if(numberOfSides == 4) {
            return new Square();
        }
        if(numberOfSides == 5) {
            return new Pentagon();
        }
        if(numberOfSides == 7) {
            return new Heptagon();
        }
        else if(numberOfSides == 8) {
            return new Octagon();
        }
        return null;
    }
}

注意客户端如何依赖这个工厂来给我们一个合适的Polygon,而不必直接初始化对象。

4.2. 何时使用工厂方法设计模式

  • 当接口或抽象类的实现预计会频繁更改时
  • 当当前的实现不能舒适地适应新的变化时
  • 当初始化过程比较简单,构造函数只需要少量参数时

5. 抽象工厂设计模式

在上一节中,我们看到了如何使用工厂方法设计模式来创建与单个系列相关的对象。

相比之下,抽象工厂设计模式用于创建相关或依赖对象的系列。它有时也被称为工厂工厂。

有关详细说明,请查看我们的抽象工厂 教程。

6. 建造者设计模式

Builder 设计模式是另一种创建模式,旨在处理相对复杂的对象的构造。

当创建对象的复杂性增加时,Builder模式可以通过使用另一个对象(builder)来构造对象,从而分离出实例化过程。

然后可以使用此构建器通过简单的逐步方法创建许多其他类似的表示。

6.1.构建器模式示例

GoF 引入的原始 Builder Design Pattern 侧重于抽象,在处理复杂对象时非常好,但是设计有点复杂。

Joshua Bloch 在他的《Effective Java》一书中介绍了构建器模式的改进版本,它干净、可读性强(因为它使用了流畅的设计 )并且从客户的角度来看很容易使用。在本例中,我们将讨论该版本。

此示例只有一个类BankAccount,其中包含一个构建器作为private内部类:

public class BankAccount {

    private String name;
    private String accountNumber;
    private String email;
    private boolean newsletter;
    // constructors/getters

    public static class BankAccountBuilder {
        // builder code
    }
}

请注意,字段上的所有访问修饰符都被声明为私有,因为我们不希望外部对象直接访问它们。

构造函数也是private的,因此只有分配给此类的 Builder 才能访问它。构造函数中设置的所有属性都是从我们作为参数提供的构建器对象中提取的。

我们在static内部类中定义了BankAccountBuilder

public static class BankAccountBuilder {

    private String name;
    private String accountNumber;
    private String email;
    private boolean newsletter;

    public BankAccountBuilder(String name, String accountNumber) {
        this.name = name;
        this.accountNumber = accountNumber;
    }
    public BankAccountBuilder withEmail(String email) {
        this.email = email;
        return this;
    }
    public BankAccountBuilder wantNewsletter(boolean newsletter) {
        this.newsletter = newsletter;
        return this;
    }

    public BankAccount build() {
        return new BankAccount(this);
    }
}

请注意,我们已经声明了外部类包含的相同字段集。任何必填字段都需要作为内部类构造函数的参数,而剩余的可选字段可以使用 setter 方法指定。

此实现还通过让 setter 方法返回构建器对象来支持流畅的设计方法。

最后,build 方法调用外部类的私有构造函数,并将自身作为参数传递。返回的BankAccount将使用BankAccountBuilder设置的参数进行实例化。

让我们看一个构建器模式的快速示例:

BankAccount newAccount = new BankAccount
  .BankAccountBuilder("Jon", "22738022275")
  .withEmail("Jon@example.com")
  .wantNewsletter(true)
  .build();

6.2. 何时使用构建器模式

  1. 当创建对象的过程非常复杂,有很多强制和可选参数时
  2. 当构造函数参数数量增加导致构造函数列表很大时
  3. 当客户端期望构造的对象有不同的表示时