1. 引言
在软件工程中,设计模式描述了软件设计中最常见问题的成熟解决方案。它代表了经验丰富的开发者通过长期试错总结出的最佳实践。
设计模式在1994年因《设计模式:可复用面向对象软件的基础》一书而流行,该书由Erich Gamma、John Vlissides、Ralph Johnson和Richard Helm(四人帮,GoF)合著。
本文将深入探讨创建型设计模式及其类型,通过代码示例分析这些模式的适用场景。
2. 创建型设计模式
创建型设计模式专注于对象的创建方式。它们通过受控的方式创建对象,降低复杂性和不稳定性。
new
操作符通常被认为是有害的,因为它会在应用程序中分散对象。随着时间推移,类之间紧密耦合会导致实现变更困难。
创建型设计模式通过将客户端与实际初始化过程完全解耦来解决此问题。
本文将讨论四种创建型设计模式:
- 单例模式 - 确保整个应用中最多只存在一个对象实例
- 工厂方法模式 - 创建多个相关类的对象,无需指定具体创建的对象
- 抽象工厂模式 - 创建相关依赖对象的家族
- 建造者模式 - 通过分步方式构建复杂对象
接下来详细讨论每种模式。
3. 单例设计模式
单例设计模式的目标是确保整个Java虚拟机中只存在一个特定类的对象实例。
单例类还提供唯一的全局访问点,确保每次调用该访问点都返回同一个对象。
3.1 单例模式示例
虽然单例模式由GoF提出,但原始实现在多线程场景下存在问题。
这里采用更优的静态内部类方案:
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
public static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
我们创建了一个持有Singleton
实例的静态内部类。实例仅在调用getInstance()
方法时创建,而非外部类加载时。
这是广泛使用的单例实现方案,因为:
- 无需同步
- 线程安全
- 支持懒加载
- 样板代码较少
⚠️ 注意构造函数是private
的。这是创建单例的必要条件,因为public
构造函数意味着任何人都能创建新实例。
这不是GoF的原始实现,原始版本可参考这篇Baeldung文章。
3.2 何时使用单例模式
✅ 创建成本高昂的资源(如数据库连接对象)
✅ 将所有日志记录器保持为单例可提升性能
✅ 提供应用配置设置访问的类
✅ 包含共享模式访问资源的类
4. 工厂方法设计模式
工厂设计模式或工厂方法设计模式是Java中最常用的设计模式之一。
根据GoF定义,该模式**"定义一个创建对象的接口,但让子类决定实例化哪个类。工厂方法让类的实例化延迟到子类"**。
该模式通过创建虚拟构造器,将初始化类的责任从客户端转移到特定工厂类。
我们依赖工厂提供对象,隐藏实际实现细节。创建的对象通过通用接口访问。
4.1 工厂方法模式示例
本例创建一个Polygon
接口,由多个具体类实现。使用PolygonFactory
获取该家族的对象:
首先创建Polygon
接口:
public interface Polygon {
String getType();
}
接着创建实现类如Square
、Triangle
等,返回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. 建造者设计模式
建造者设计模式是另一种创建型模式,用于处理相对复杂对象的构建。
当对象创建的复杂度增加时,建造者模式可以通过另一个对象(建造者)分离实例化过程。
该建造者可通过简单的分步方法创建许多其他类似表示。
6.1 建造者模式示例
GoF提出的原始建造者模式侧重抽象,处理复杂对象时效果很好,但设计稍显复杂。
Joshua Bloch在《Effective Java》中提出了改进版本,更简洁、可读性高(因采用流式设计),且客户端使用方便。本例讨论该版本。
示例只有一个BankAccount
类,包含一个作为静态内部类的建造者:
public class BankAccount {
private String name;
private String accountNumber;
private String email;
private boolean newsletter;
// 构造函数/getters
public static class BankAccountBuilder {
// 建造者代码
}
}
所有字段声明为private
,防止外部对象直接访问。
构造函数也是private
,只有分配给该类的建造者能访问它。构造函数中的所有属性都从作为参数传入的建造者对象中提取。
我们在静态内部类中定义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 何时使用建造者模式
✅ 对象创建过程极其复杂,包含大量必填和可选参数时
✅ 构造函数参数增加导致构造函数列表过长时
✅ 客户端需要构建对象的不同表示时
7. 结论
本文学习了Java中的创建型设计模式,讨论了四种类型:单例、工厂方法、抽象工厂和建造者模式,分析了它们的优缺点、示例及适用场景。
完整代码片段可在GitHub获取。