一、设计模式概述
1.1 设计模式的概念
设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它并不是一种语法规定,而是前辈们在长期的软件开发实践中,针对各种常见问题所总结出来的通用解决方案。通过使用设计模式,开发者能够复用代码,使代码更易于被他人理解,从而保证代码的可靠性,提高软件系统的可维护性和可扩展性。
在软件开发过程中,我们常常会遇到各种各样的问题,例如如何创建对象、如何组织对象之间的关系、如何实现对象之间的交互等。设计模式就像是一把把钥匙,为这些问题提供了行之有效的解决方案。它是对面向对象编程中类的封装性、继承性和多态性,以及类的关联关系和组合关系的充分理解和应用。
1.2 设计模式分类
设计模式通常分为三大类:创建型模式、结构型模式和行为型模式。
- 创建型模式:主要用于对象的创建过程,它隐藏了对象的创建逻辑,将对象的创建和使用分离,使得代码在创建对象时更加灵活和可维护。常见的创建型模式有单例模式、工厂模式(包括简单工厂模式、工厂方法模式、抽象工厂模式)、建造者模式和原型模式。以单例模式为例,它保证一个类仅有一个实例,并提供一个访问它的全局访问点,在一些需要全局唯一控制的场景,如系统配置管理、日志记录器等非常有用。
- 结构型模式:关注的是如何将类或对象组合成更大的结构,以实现特定的功能和职责。它主要用于处理类与对象之间的组合关系,通过合理地组合和装配对象,使得系统的结构更加清晰、灵活和易于维护。常见的结构型模式包括适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式和代理模式。比如适配器模式,它可以将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以协同工作,就像电源适配器可以将不同国家的电压标准转换为设备所需的电压一样。
- 行为型模式:主要用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责。它侧重于对象之间的交互和职责分配,通过定义对象之间的通信和协作方式,使得系统能够更好地应对复杂的业务逻辑和变化。常见的行为型模式有模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式和访问者模式。例如观察者模式,它定义了一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都能得到通知并自动更新,在消息通知、事件驱动等场景中应用广泛。
1.3 设计模式的原则
设计模式遵循一系列的设计原则,这些原则是指导我们进行良好软件设计的基石,以下是几个重要的原则:
- 开闭原则(Open - Closed Principle,OCP):软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。这意味着在设计软件时,应该通过扩展代码来实现新功能,而不是直接修改已有的代码。这样可以减少对原有代码的影响,降低引入新错误的风险,提高软件的稳定性和可维护性。例如,在一个图形绘制系统中,已经定义了一个绘制圆形的类Circle,如果需要增加绘制矩形的功能,按照开闭原则,我们不应该直接修改Circle类,而是创建一个新的Rectangle类来实现绘制矩形的功能,同时让Rectangle类和Circle类都实现一个统一的图形绘制接口Shape。这样,当需要增加新的图形类型时,只需要创建新的类实现Shape接口即可,而不需要修改已有的Circle类和其他相关代码。
- 单一职责原则(Single Responsibility Principle,SRP):一个类应该仅有一个引起它变化的原因,即一个类只负责一项职责。当一个类承担过多的职责时,它就会变得复杂,难以理解和维护,并且一个职责的变化可能会影响到其他职责。遵循单一职责原则可以降低类的复杂度,提高类的可读性和可维护性。比如在一个电商系统中,订单管理和用户管理是两个不同的职责,应该分别由OrderManager类和UserManager类来负责,而不是将这两个职责都放在一个类中。如果将订单管理和用户管理的功能都放在一个类中,当订单管理的业务逻辑发生变化时,可能会不小心影响到用户管理的功能,而且这样的类代码也会显得混乱,难以阅读和维护。
- 里氏替换原则(Liskov Substitution Principle,LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。也就是说,子类对象可以替换父类对象,并且不会影响程序的正确性。这一原则是对继承关系的一种约束,确保子类在扩展父类功能的同时,不会破坏父类原有的行为约定。例如,有一个父类Animal,其中定义了eat方法,子类Dog继承自Animal,那么在任何使用Animal对象的地方,都可以用Dog对象来替换,并且Dog对象调用eat方法时,其行为应该与Animal对象调用eat方法时的行为保持一致或者是合理的扩展,而不能出现与父类不一致的行为,否则就违反了里氏替换原则。
- 依赖倒置原则(Dependence Inversion Principle,DIP):高层模块不应该依赖底层模块,二者都应该依赖其抽象;抽象不依赖细节,细节依赖抽象。在实际编程中,应该面向接口编程,而不是面向实现编程。这样可以降低模块之间的耦合度,提高系统的灵活性和可维护性。例如,在一个订单处理系统中,订单处理模块(高层模块)不应该直接依赖于具体的支付实现类(底层模块),而是依赖于一个支付接口(抽象)。具体的支付实现类,如支付宝支付类、微信支付类等,实现这个支付接口。当需要更换支付方式时,只需要创建新的实现支付接口的类,而不需要修改订单处理模块的代码,只需要在配置或者实例化时使用新的支付实现类即可,这就体现了依赖倒置原则的应用。
- 接口隔离原则(Interface Segregation Principle,ISP):客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。使用多个专门的接口比使用一个大而全的接口要好,这样可以降低类之间的耦合度,提高系统的灵活性和可维护性。比如在一个图形绘制系统中,有一个Shape接口,如果这个接口中既包含绘制图形的方法,又包含计算图形面积、周长等方法,对于一些只需要绘制图形功能的客户端来说,就会依赖到一些不需要的方法,造成不必要的耦合。按照接口隔离原则,应该将Shape接口拆分成多个专门的接口,如DrawShape接口用于绘制图形,CalculateShape接口用于计算图形的面积、周长等,这样不同的客户端就可以只依赖自己需要的接口。
- 迪米特法则(Law of Demeter,LoD):一个对象应该对其他对象有尽可能少的了解,也称为最少知道原则。它强调的是降低类之间的耦合度,提高模块的独立性。当一个类与其他类的交互过多时,会导致类之间的依赖关系复杂,维护起来困难。例如,在一个公司管理系统中,员工类只需要与自己直接相关的部门类和上级领导类进行交互,而不应该直接与公司的其他所有类都有复杂的交互关系。如果员工类知道太多其他类的细节,当其他类发生变化时,很可能会影响到员工类,违反了迪米特法则。通过遵循迪米特法则,可以使系统的结构更加清晰,各个模块之间的依赖关系更加简单,从而提高系统的可维护性和可扩展性。
二、单例模式实战
2.1 单例模式概念
单例模式是一种创建型设计模式,它的核心作用是保证一个类在整个系统中只有一个实例存在,并且为这个实例提供一个全局的访问点。在实际应用中,有些资源或对象在系统中只需要一份,比如数据库连接池、系统配置信息、日志记录器等。如果创建多个实例,不仅会浪费系统资源,还可能导致数据不一致等问题。单例模式通过将类的构造函数私有化,使得外部无法直接创建该类的实例,同时提供一个静态方法来获取唯一的实例,从而实现了对实例数量的严格控制和全局访问的便捷性。
2.2 单例模式实现方式
- 饿汉式:饿汉式单例在类加载时就创建实例,是线程安全的。因为类加载机制保证了在多线程环境下,类的静态成员只会被初始化一次。代码示例如下:
public class SingletonEager {
// 私有静态实例,在类加载时就初始化
private static final SingletonEager instance = new SingletonEager();
// 私有构造函数,防止外部实例化
private SingletonEager() {}
// 公共静态方法提供全局访问
public static SingletonEager getInstance() {
return instance;
}
}
这种方式实现简单,缺点是如果实例化的对象比较重,且并不总是需要时,可能会造成资源浪费,因为不管是否使用这个实例,它在类加载时就已经被创建了。
- 懒汉式(线程不安全):懒汉式单例在第一次使用时才创建实例,节省资源。但在多线程环境下,如果多个线程同时调用getInstance方法,可能会创建多个实例,从而破坏单例模式。代码示例如下:
public class SingletonLazyUnsafe {
// 未初始化实例,懒汉式初始化
private static SingletonLazyUnsafe instance;
// 私有构造方法,防止外部直接实例化
private SingletonLazyUnsafe() {}
// 公共静态方法提供全局访问
public static SingletonLazyUnsafe getInstance() {
if (instance == null) {
instance = new SingletonLazyUnsafe();
}
return instance;
}
}
- 懒汉式(线程安全,synchronized 方法):为了解决懒汉式线程不安全的问题,可以在getInstance方法上加上synchronized关键字,这样每次只有一个线程可以进入该方法,从而保证了线程安全。但这种方式会导致每次调用getInstance方法时都需要进行同步操作,性能较低。代码示例如下:
public class SingletonLazySafe {
// 未初始化实例,懒汉式初始化
private static SingletonLazySafe instance;
// 私有构造方法,防止外部直接实例化
private SingletonLazySafe() {}
// 公共静态方法提供全局访问,使用synchronized保证线程安全
public static synchronized SingletonLazySafe getInstance() {
if (instance == null) {
instance = new SingletonLazySafe();
}
return instance;
}
}
- 双重校验锁(DCL, Double Checked Locking):双重校验锁在懒汉式基础上进行了优化,减少了同步开销。它通过两次检查instance是否为null,第一次检查是为了避免不必要的同步,只有在instance为null时才进入同步代码块;第二次检查是为了确保在多线程环境下,即使多个线程同时通过了第一次检查,也只有一个线程能够创建实例。同时,使用volatile关键字修饰instance,保证了内存可见性和禁止指令重排。代码示例如下:
public class SingletonDCL {
// 使用volatile保证线程可见性和禁止指令重排
private static volatile SingletonDCL instance;
// 私有构造方法,防止外部直接实例化
private SingletonDCL() {}
// 公共静态方法提供全局访问
public static SingletonDCL getInstance() {
if (instance == null) { // 第一重检查
synchronized (SingletonDCL.class) {
if (instance == null) { // 第二重检查
instance = new SingletonDCL();
}
}
}
return instance;
}
}
这种方式既保证了线程安全,又提高了性能,是一种比较常用的单例模式实现方式。但它的实现相对复杂,需要对volatile和双重检查机制有深入的理解。
2.3 单例模式线程安全问题
在多线程环境下,单例模式如果不进行适当的处理,就会出现线程安全问题。以懒汉式(线程不安全)的实现方式为例,当多个线程同时调用getInstance方法时,可能会出现以下情况:假设线程 A 和线程 B 同时调用getInstance方法,此时instance为null,两个线程都通过了if (instance == null)的判断,然后线程 A 创建了一个实例,接着线程 B 也创建了一个实例,这样就导致系统中出现了两个Singleton实例,破坏了单例模式的唯一性。
为了解决线程安全问题,我们可以使用前面提到的几种线程安全的实现方式,如饿汉式、懒汉式(使用synchronized方法)、双重校验锁等。其中,双重校验锁中使用volatile关键字修饰单例实例变量,这是非常关键的。因为volatile关键字有两个重要作用:一是保证内存可见性,即当一个线程修改了volatile修饰的变量的值,其他线程能够立即看到这个修改;二是禁止指令重排,在创建对象的过程中,new操作并非原子操作,它分为三步:分配对象内存、调用构造器方法执行初始化、将对象引用赋值给变量。如果没有volatile修饰,虚拟机可能会对这三步进行指令重排,例如先执行分配内存和将对象引用赋值给变量,然后再执行构造器初始化。这样在多线程环境下,就可能出现一个线程获取到了一个未完全初始化的对象,从而导致程序出错。而volatile关键字可以禁止这种指令重排,确保对象在被其他线程访问之前已经完全初始化。
2.4 单例模式实战案例
在实际项目中,配置管理类是单例模式的一个常见应用场景。假设我们有一个应用程序,需要读取和管理一些配置信息,如数据库连接地址、用户名、密码、日志级别等。这些配置信息在整个应用程序中是全局共享的,并且只需要一份实例。下面是一个使用单例模式实现的配置管理类示例:
public class ConfigManager {
// 使用volatile保证线程可见性和禁止指令重排
private static volatile ConfigManager instance;
private String dbUrl;
private String dbUsername;
private String dbPassword;
private String logLevel;
// 私有构造方法,防止外部直接实例化
private ConfigManager() {
// 模拟从配置文件读取配置信息,实际应用中可以使用Properties类等方式读取配置文件
dbUrl = "jdbc:mysql://localhost:3306/mydb";
dbUsername = "root";
dbPassword = "password";
logLevel = "INFO";
}
// 公共静态方法提供全局访问
public static ConfigManager getInstance() {
if (instance == null) {
synchronized (ConfigManager.class) {
if (instance == null) {
instance = new ConfigManager();
}
}
}
return instance;
}
public String getDbUrl() {
return dbUrl;
}
public String getDbUsername() {
return dbUsername;
}
public String getDbPassword() {
return dbPassword;
}
public String getLogLevel() {
return logLevel;
}
}
在其他类中,可以通过以下方式获取配置管理类的实例并使用其中的配置信息:
public class App {
public static void main(String[] args) {
ConfigManager configManager = ConfigManager.getInstance();
System.out.println("数据库连接地址: " + configManager.getDbUrl());
System.out.println("数据库用户名: " + configManager.getDbUsername());
System.out.println("数据库密码: " + configManager.getDbPassword());
System.out.println("日志级别: " + configManager.getLogLevel());
}
}
通过这种方式,整个应用程序中只有一个ConfigManager实例,所有需要使用配置信息的地方都可以通过ConfigManager.getInstance()获取该实例,从而保证了配置信息的一致性和全局访问的便捷性,同时也避免了多次读取配置文件带来的性能开销和资源浪费。
三、工厂模式实战
3.1 简单工厂模式
简单工厂模式是工厂模式的基础版本,它不属于 GoF 设计模式中的 23 种经典模式,但它为工厂模式的理解和应用奠定了基础。简单工厂模式定义了一个工厂类,该工厂类有一个创建产品对象的方法,它根据传入的参数决定创建哪种具体的产品对象。
简单工厂模式包含三个主要角色:
- 抽象产品(Abstract Product):定义了产品的公共接口或抽象类,它是具体产品的父类或接口,规定了产品对象应具备的方法。例如,在一个图形绘制系统中,可能定义一个抽象的Shape接口,其中包含draw方法用于绘制图形。
- 具体产品(Concrete Product):实现了抽象产品接口的具体类,每个具体产品类都有自己独特的实现逻辑。如Circle类和Rectangle类实现了Shape接口,分别实现了绘制圆形和矩形的draw方法。
- 工厂类(Factory):负责创建产品对象的类,它包含一个创建产品的方法,根据传入的参数决定创建哪种具体产品。比如ShapeFactory类,它有一个静态方法getShape,根据传入的形状类型参数(如"CIRCLE"或"RECTANGLE")返回相应的Circle或Rectangle对象。
以下是简单工厂模式的代码示例:
// 抽象产品:形状接口
interface Shape {
void draw();
}
// 具体产品:圆形
class Circle implements Shape {
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
// 具体产品:矩形
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("绘制矩形");
}
}
// 工厂类
class ShapeFactory {
// 根据类型创建形状对象
public static Shape getShape(String shapeType) {
if ("CIRCLE".equalsIgnoreCase(shapeType)) {
return new Circle();
} else if ("RECTANGLE".equalsIgnoreCase(shapeType)) {
return new Rectangle();
}
return null;
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Shape circle = ShapeFactory.getShape("CIRCLE");
circle.draw();
Shape rectangle = ShapeFactory.getShape("RECTANGLE");
rectangle.draw();
}
}
在上述代码中,ShapeFactory类作为工厂类,封装了创建Shape对象的逻辑。客户端只需要调用ShapeFactory.getShape方法并传入相应的参数,就可以获取到所需的形状对象,而无需关心对象的具体创建过程,从而实现了对象创建和使用的解耦。
简单工厂模式的优点在于实现简单,将对象的创建和使用分离,使得代码结构更加清晰。然而,它也存在一些缺点,当需要添加新的产品时,需要修改工厂类的创建方法,这违背了开闭原则。而且工厂类的职责过重,集中了所有产品的创建逻辑,不符合单一职责原则。因此,简单工厂模式适用于创建对象逻辑简单、产品种类较少且不经常变化的场景。
3.2 工厂方法模式
工厂方法模式是对简单工厂模式的进一步抽象和扩展。在工厂方法模式中,不再由一个工厂类负责创建所有的产品对象,而是将创建对象的方法抽象成抽象方法,由具体的工厂子类来实现。这样,当需要增加新的产品时,只需要增加一个具体的产品类和对应的具体工厂类,而不需要修改工厂类的代码,符合开闭原则。
工厂方法模式包含四个主要角色:
- 抽象产品(Abstract Product):与简单工厂模式中的抽象产品角色相同,定义了产品的公共接口或抽象类。
- 具体产品(Concrete Product):实现抽象产品接口的具体类,每个具体产品类都有自己独特的实现逻辑。
- 抽象工厂(Abstract Factory):定义了创建产品对象的抽象方法,它是具体工厂类的父类或接口。
- 具体工厂(Concrete Factory):实现抽象工厂接口的具体类,负责创建具体的产品对象。
以下是工厂方法模式的代码示例:
// 抽象产品:产品接口
interface Product {
void use();
}
// 具体产品A
class ConcreteProductA implements Product {
@Override
public void use() {
System.out.println("使用产品A");
}
}
// 具体产品B
class ConcreteProductB implements Product {
@Override
public void use() {
System.out.println("使用产品B");
}
}
// 抽象工厂:定义创建产品的抽象方法
interface ProductFactory {
Product createProduct();
}
// 具体工厂A:创建产品A
class ProductAFactory implements ProductFactory {
@Override
public Product createProduct() {
return new ConcreteProductA();
}
}
// 具体工厂B:创建产品B
class ProductBFactory implements ProductFactory {
@Override
public Product createProduct() {
return new ConcreteProductB();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
ProductFactory factoryA = new ProductAFactory();
Product productA = factoryA.createProduct();
productA.use();
ProductFactory factoryB = new ProductBFactory();
Product productB = factoryB.createProduct();
productB.use();
}
}
在这个示例中,ProductFactory接口定义了创建产品的抽象方法createProduct,ProductAFactory和ProductBFactory分别是创建ConcreteProductA和ConcreteProductB的具体工厂类。客户端通过具体的工厂类来创建所需的产品对象,实现了对象创建和使用的进一步解耦,并且提高了系统的可扩展性。当需要添加新的产品时,只需要创建新的具体产品类和对应的具体工厂类,而不需要修改已有的代码。例如,如果要添加产品 C,只需要创建ConcreteProductC类实现Product接口,以及ProductCFactory类实现ProductFactory接口即可。
3.3 抽象工厂模式
抽象工厂模式提供了一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。它与工厂方法模式的主要区别在于,工厂方法模式创建的是单一产品,而抽象工厂模式创建的是一组相关产品,这些产品构成一个产品族。
抽象工厂模式包含以下角色:
- 抽象产品(Abstract Product):定义了产品的接口,每个产品族中的产品都有对应的抽象产品接口。例如,在一个跨平台 UI 组件库中,可能有按钮Button和文本框TextBox的抽象接口。
- 具体产品(Concrete Product):实现了抽象产品接口的具体类,每个具体产品类对应一个具体的产品族。如WinButton和MacButton分别是 Windows 和 Mac 平台下的按钮实现类,它们都实现了Button抽象接口。
- 抽象工厂(Abstract Factory):声明了一组创建抽象产品的方法,每个方法对应创建一个产品族中的一种产品。例如,IUIFactory接口中可能声明了createButton和createTextBox方法,分别用于创建按钮和文本框。
- 具体工厂(Concrete Factory):实现了抽象工厂接口,创建具体产品族中的所有产品。如WinFactory是 Windows 平台的具体工厂,实现了IUIFactory接口,创建WinButton和WinTextBox等 Windows 风格的 UI 组件。
以下是抽象工厂模式的代码示例:
// 抽象产品:按钮接口
interface Button {
void render();
}
// 抽象产品:文本框接口
interface TextBox {
void render();
}
// 具体产品:Windows按钮
class WinButton implements Button {
@Override
public void render() {
System.out.println("渲染Windows风格的按钮");
}
}
// 具体产品:Windows文本框
class WinTextBox implements TextBox {
@Override
public void render() {
System.out.println("渲染Windows风格的文本框");
}
}
// 具体产品:Mac按钮
class MacButton implements Button {
@Override
public void render() {
System.out.println("渲染Mac风格的按钮");
}
}
// 具体产品:Mac文本框
class MacTextBox implements TextBox {
@Override
public void render() {
System.out.println("渲染Mac风格的文本框");
}
}
// 抽象工厂:定义创建按钮和文本框的抽象方法
interface IUIFactory {
Button createButton();
TextBox createTextBox();
}
// 具体工厂:Windows工厂
class WinFactory implements IUIFactory {
@Override
public Button createButton() {
return new WinButton();
}
@Override
public TextBox createTextBox() {
return new WinTextBox();
}
}
// 具体工厂:Mac工厂
class MacFactory implements IUIFactory {
@Override
public Button createButton() {
return new MacButton();
}
@Override
public TextBox createTextBox() {
return new MacTextBox();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
IUIFactory winFactory = new WinFactory();
Button winButton = winFactory.createButton();
TextBox winTextBox = winFactory.createTextBox();
winButton.render();
winTextBox.render();
IUIFactory macFactory = new MacFactory();
Button macButton = macFactory.createButton();
TextBox macTextBox = macFactory.createTextBox();
macButton.render();
macTextBox.render();
}
}
在上述代码中,IUIFactory接口定义了创建按钮和文本框的抽象方法,WinFactory和MacFactory分别是创建 Windows 和 Mac 风格 UI 组件的具体工厂。客户端通过具体工厂创建相应产品族的对象,确保了同一产品族中产品之间的兼容性和一致性。例如,使用WinFactory创建的WinButton和WinTextBox都是 Windows 风格的组件,它们在外观和行为上是相互匹配的。抽象工厂模式的优点是可以确保创建的对象之间的兼容性,提高了代码的可维护性和可扩展性,特别适用于创建一组相关产品的场景。但缺点是实现较为复杂,当产品族或产品种类发生变化时,可能需要修改抽象工厂接口和所有具体工厂类,违背了开闭原则。
3.4 工厂模式实战案例
- 日志工厂:以 MyBatis 框架中的日志工厂为例,MyBatis 作为一个第三方框架,需要与应用程序使用一致的日志体系,以避免出现多种日志框架并存的混乱情况。MyBatis 内置了多种日志实现,如log4j、log4j2、Commons Logging、Slf4j、Jul(Java Util Logging)、Logback等。LogFactory类作为日志工厂,其核心作用是简化日志框架的选择。在LogFactory的静态代码块中,会依次尝试初始化不同日志类的构造器,按照一定的顺序(如先尝试Slf4j,再尝试Commons Logging等)进行初始化。如果某个日志框架的构造器初始化成功,就会使用该日志框架。在 MyBatis 的类中,通过调用LogFactory.getLog方法并传入类名或日志名称,就可以获取对应的日志实例,从而实现日志记录功能。这样,应用程序开发者可以根据自己的需求选择合适的日志框架,而 MyBatis 框架只需通过LogFactory来创建日志实例,无需关心具体的日志框架实现细节,实现了日志创建和使用的解耦。
- 数据库连接工厂:在开发数据库相关应用时,可能需要连接不同类型的数据库,如 MySQL、Oracle、SQL Server 等。使用工厂模式可以方便地管理数据库连接的创建。以简单工厂模式为例,首先定义一个抽象的数据库连接接口DatabaseConnection,其中包含连接数据库的抽象方法connect。然后分别创建具体的数据库连接类,如MySQLConnection、OracleConnection,它们实现了DatabaseConnection接口,并实现了各自的connect方法,用于连接对应的数据库。接着创建一个工厂类DatabaseConnectionFactory,它有一个静态方法getConnection,根据传入的数据库类型参数(如"mysql"或"oracle")返回相应的数据库连接对象。在客户端代码中,通过调用DatabaseConnectionFactory.getConnection方法并传入数据库类型,就可以获取到所需的数据库连接对象,然后使用该连接对象进行数据库操作,如执行 SQL 语句等。这样,当需要更换数据库类型时,只需要修改工厂类中创建连接对象的逻辑,而不需要修改大量的业务代码,提高了代码的可维护性和可扩展性。如果采用工厂方法模式或抽象工厂模式,可以进一步提高代码的灵活性和可扩展性,适应更复杂的数据库连接场景,如同时支持多种数据库类型的事务处理等。
四、建造者模式与原型模式
4.1 建造者模式
建造者模式是一种创建型设计模式,它将复杂对象的构建过程与表示分离,使得同样的构建过程可以创建不同的表示。当一个对象的构建过程涉及多个步骤,且这些步骤之间有依赖关系时,传统的构造函数方式会使代码变得复杂且难以维护,尤其是在有多个可选参数的情况下,构造函数可能会变得冗长,影响代码的可读性。而建造者模式通过将构建过程分离,使得创建复杂对象变得更加清晰和灵活,符合开闭原则。
建造者模式主要包含以下几个角色:
- 产品(Product):表示要创建的复杂对象,它包含多个组成部分。例如在一个房屋建造的场景中,House就是产品,它包含地基foundation、墙壁walls、屋顶roof和楼层floors等组成部分。
- 抽象建造者(Builder):定义创建产品的抽象接口,声明构建产品各个部分的方法。在房屋建造场景中,HouseBuilder是抽象建造者,它声明了buildFoundation(建造地基)、buildWalls(建造墙壁)、buildRoof(建造屋顶)和buildFloors(建造楼层)等方法。
- 具体建造者(ConcreteBuilder):实现建造者接口,提供构建产品的具体实现。如ConcreteHouseBuilder是具体建造者,它实现了HouseBuilder接口,具体实现了如何建造地基、墙壁、屋顶和楼层,比如将地基设置为"Concrete Foundation",将墙壁设置为"Brick Walls"等。
- 指挥者(Director):负责控制建造过程,按照特定的顺序调用建造者的方法。在房屋建造中,HouseDirector是指挥者,它持有一个HouseBuilder对象,通过调用builder.buildFoundation()、builder.buildWalls()、builder.buildRoof()和builder.buildFloors(2)(假设默认建造 2 层)等方法,按照一定的顺序构建房屋。
以下是一个简单的建造者模式代码示例:
// 产品类:房屋
class House {
private String foundation;
private String walls;
private String roof;
private int floors;
public void setFoundation(String foundation) {
this.foundation = foundation;
}
public void setWalls(String walls) {
this.walls = walls;
}
public void setRoof(String roof) {
this.roof = roof;
}
public void setFloors(int floors) {
this.floors = floors;
}
@Override
public String toString() {
return "House{" +
"foundation='" + foundation + '\'' +
", walls='" + walls + '\'' +
", roof='" + roof + '\'' +
", floors=" + floors +
'}';
}
}
// 抽象建造者接口
interface HouseBuilder {
void buildFoundation();
void buildWalls();
void buildRoof();
void buildFloors(int floors);
House build();
}
// 具体建造者类
class ConcreteHouseBuilder implements HouseBuilder {
private House house;
public ConcreteHouseBuilder() {
this.house = new House();
}
@Override
public void buildFoundation() {
house.setFoundation("Concrete Foundation");
}
@Override
public void buildWalls() {
house.setWalls("Brick Walls");
}
@Override
public void buildRoof() {
house.setRoof("Gable Roof");
}
@Override
public void buildFloors(int floors) {
house.setFloors(floors);
}
@Override
public House build() {
return house;
}
}
// 指挥者类
class HouseDirector {
private HouseBuilder builder;
public HouseDirector(HouseBuilder builder) {
this.builder = builder;
}
public House constructHouse() {
builder.buildFoundation();
builder.buildWalls();
builder.buildRoof();
builder.buildFloors(2); // 默认构建2层
return builder.build();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
HouseBuilder builder = new ConcreteHouseBuilder();
HouseDirector director = new HouseDirector(builder);
House house = director.constructHouse();
System.out.println(house);
}
}
在上述代码中,客户端通过HouseDirector来构建House对象,HouseDirector按照一定的顺序调用ConcreteHouseBuilder的方法来完成房屋的建造过程,最终返回一个完整的House对象。这样,对象的构建过程和表示被分离,使得代码更加清晰、可维护和可扩展。如果需要创建不同类型的房屋,只需要创建新的具体建造者类实现HouseBuilder接口即可,而不需要修改HouseDirector和其他相关代码。
4.2 建造者模式实战案例
以用户订单对象构建为例,在电商系统中,订单创建是一个复杂的过程,涉及多个必填和可选参数。例如:
- 必填项:订单 ID、用户 ID、商品列表、总金额。
- 可选项:优惠信息、发票信息、收货地址、备注。
- 约束条件:订单 ID 必须唯一;商品列表不能为空;总金额必须与商品金额总和匹配;如果使用优惠券,必须验证有效性。
如果使用传统的构造器或setter方法,会导致构造器参数过多难以维护,setter方法无法保证对象创建过程的原子性,参数验证逻辑分散等问题。而使用建造者模式可以很好地解决这些问题。以下是使用建造者模式实现用户订单对象构建的代码示例:
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
// 订单实体类
public class Order {
// 必填字段
private final String orderId;
private final Long userId;
private final List<OrderItem> items;
private final BigDecimal totalAmount;
// 可选字段
private final CouponInfo coupon;
private final InvoiceInfo invoice;
private final ShippingAddress address;
private final String remark;
// 私有构造器,只能通过Builder创建
private Order(Builder builder) {
this.orderId = builder.orderId;
this.userId = builder.userId;
this.items = Collections.unmodifiableList(builder.items);
this.totalAmount = builder.totalAmount;
this.coupon = builder.coupon;
this.invoice = builder.invoice;
this.address = builder.address;
this.remark = builder.remark;
}
// getter方法
public String getOrderId() {
return orderId;
}
public Long getUserId() {
return userId;
}
public List<OrderItem> getItems() {
return items;
}
public BigDecimal getTotalAmount() {
return totalAmount;
}
public CouponInfo getCoupon() {
return coupon;
}
public InvoiceInfo getInvoice() {
return invoice;
}
public ShippingAddress getAddress() {
return address;
}
public String getRemark() {
return remark;
}
// 建造者静态内部类
public static class Builder {
// 必填参数
private final String orderId;
private final Long userId;
// 可选参数
private List<OrderItem> items = new ArrayList<>();
private BigDecimal totalAmount = BigDecimal.ZERO;
private CouponInfo coupon;
private InvoiceInfo invoice;
private ShippingAddress address;
private String remark;
public Builder(String orderId, Long userId) {
this.orderId = Objects.requireNonNull(orderId);
this.userId = Objects.requireNonNull(userId);
}
public Builder items(List<OrderItem> items) {
this.items = new ArrayList<>(items); // 防御性拷贝
return this;
}
public Builder addItem(OrderItem item) {
this.items.add(Objects.requireNonNull(item));
return this;
}
public Builder totalAmount(BigDecimal amount) {
this.totalAmount = Objects.requireNonNull(amount);
return this;
}
public Builder coupon(CouponInfo coupon) {
this.coupon = coupon;
return this;
}
public Builder invoice(InvoiceInfo invoice) {
this.invoice = invoice;
return this;
}
public Builder address(ShippingAddress address) {
this.address = address;
return this;
}
public Builder remark(String remark) {
this.remark = remark;
return this;
}
public Order build() {
validate();
calculateTotal();// 自动计算总金额
return new Order(this);
}
private void validate() {
if (orderId.isEmpty()) {
throw new IllegalArgumentException("订单ID不能为空");
}
if (items.isEmpty()) {
throw new IllegalArgumentException("订单必须包含至少一件商品");
}
if (coupon != null &&!coupon.isValid()) {
throw new IllegalArgumentException("优惠券已失效");
}
}
private void calculateTotal() {
if (totalAmount.compareTo(BigDecimal.ZERO) == 0) {
// 未设置总金额时自动计算
totalAmount = items.stream()
.map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())))
.reduce(BigDecimal.ZERO, BigDecimal::add);
// 应用优惠
if (coupon != null) {
totalAmount = coupon.applyDiscount(totalAmount);
}
}
}
}
}
// 订单项实体类
class OrderItem {
private final String sku;
private final String name;
private final BigDecimal price;
private final int quantity;
public OrderItem(String sku, String name, BigDecimal price, int quantity) {
this.sku = Objects.requireNonNull(sku);
this.name = Objects.requireNonNull(name);
this.price = Objects.requireNonNull(price);
if (quantity <= 0) {
throw new IllegalArgumentException("数量必须大于0");
}
this.quantity = quantity;
}
// getter方法
public String getSku() {
return sku;
}
public String getName() {
return name;
}
public BigDecimal getPrice() {
return price;
}
public int getQuantity() {
return quantity;
}
}
// 优惠券信息类
class CouponInfo {
private final BigDecimal discountAmount;
private final boolean isValid;
public CouponInfo(BigDecimal discountAmount, boolean isValid) {
this.discountAmount = discountAmount;
this.isValid = isValid;
}
public BigDecimal applyDiscount(BigDecimal amount) {
return amount.subtract(discountAmount).max(BigDecimal.ZERO);
}
public boolean isValid() {
return isValid;
}
}
// 发票信息类
class InvoiceInfo {
// 发票相关属性和方法
private String invoiceNumber;
public InvoiceInfo(String invoiceNumber) {
this.invoiceNumber = invoiceNumber;
}
public String getInvoiceNumber() {
return invoiceNumber;
}
}
// 收货地址类
class ShippingAddress {
// 地址相关属性和方法
private String addressLine1;
private String addressLine2;
private String city;
private String state;
private String zipCode;
public ShippingAddress(String addressLine1, String addressLine2, String city, String state, String zipCode) {
this.addressLine1 = addressLine1;
this.addressLine2 = addressLine2;
this.city = city;
this.state = state;
this.zipCode = zipCode;
}
// getter方法
public String getAddressLine1() {
return addressLine1;
}
public String getAddressLine2() {
return addressLine2;
}
public String getCity() {
return city;
}
public String getState() {
return state;
}
public String getZipCode() {
return zipCode;
}
}
// 订单服务类
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.UUID;
@Service
public class OrderService {
// 模拟订单仓储
private OrderRepository orderRepository = new OrderRepository();
// 模拟库存服务
private InventoryService inventoryService = new InventoryService();
// 模拟优惠券服务
private CouponService couponService = new CouponService();
@Transactional
public String createOrder(OrderRequest request) {
// 验证库存
request.getItems().forEach(item -> inventoryService.checkStock(item.getSku(), item.getQuantity()));
// 构建订单
Order.Builder builder = Order.builder(generateOrderId(), request.getUserId());
// 添加商品项
request.getItems().forEach(item -> builder.addItem(new OrderItem(item.getSku(), item.getName(), item.getPrice(), item.getQuantity())));
// 应用优惠券
if (request.getCouponCode() != null) {
CouponInfo coupon = couponService.validateCoupon(request.getUserId(), request.getCouponCode());
builder.coupon(coupon);
}
// 设置其他信息
builder.address(request.getAddress()).remark(request.getRemark());
// 构建订单
Order order = builder.build();
// 保存订单
return orderRepository.save(order).getId();
}
private String generateOrderId() {
return UUID.randomUUID().toString();
}
}
// 订单仓储类
class OrderRepository {
public Order save(Order order) {
// 模拟保存订单到数据库
System.out.println("保存订单: " + order.getOrderId());
return order;
}
}
// 库存服务类
class InventoryService {
public void checkStock(String sku, int quantity) {
// 模拟检查库存
System.out.println("检查库存: " + sku + ",数量: " + quantity);
}
}
// 优惠券服务类
class CouponService {
public CouponInfo validateCoupon(Long userId, String couponCode) {
// 模拟验证优惠券
System.out.println("验证优惠券: " + couponCode + ",用户ID: " + userId);
// 假设返回一个有效的优惠券信息
return new CouponInfo(new BigDecimal("10.00"), true);
}
}
// 订单请求类
class OrderRequest {
private Long userId;
private List<OrderItemRequest> items;
private String couponCode;
private ShippingAddress address;
private String remark;
// getter和setter方法
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public List<OrderItemRequest> getItems() {
return items;
}
public void setItems(List<OrderItemRequest> items) {
this.items = items;
}
public String getCouponCode() {
return couponCode;
}
public void setCouponCode(String couponCode) {
this.couponCode = couponCode;
}
public ShippingAddress getAddress() {
return address;
}
public void setAddress(ShippingAddress address) {
this.address = address;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
}
// 订单项请求类
class OrderItemRequest {
private String sku;
private String name;
private BigDecimal price;
private int quantity;
// getter和setter方法
public String getSku() {
return sku;
}
public void setSku(String sku) {
this.sku = sku;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
}
在上述代码中,Order类通过静态内部类Builder来构建对象,Builder类中的方法用于设置订单的各个属性,包括必填和可选属性。在build方法中,进行参数验证和总金额计算等逻辑,最后返回一个不可变的Order对象。在OrderService中,通过Order.builder来构建订单对象,根据请求中的信息设置订单的各项属性,然后调用build方法得到完整的订单对象,最后保存订单。这种方式使得订单对象的构建过程更加清晰、灵活,并且保证了对象创建过程的原子性和参数的有效性,同时也便于维护和扩展。
4.3 原型模式(对象克隆,浅克隆与深克隆)
原型模式是创建型模式中以 “对象复制” 为核心的设计模式,其核心思想是:用已存在的对象作为原型,通过克隆(Clone)的方式创建新对象,避免重复执行复杂的对象初始化流程,尤其适用于创建成本高(如涉及数据库查询、网络请求、复杂计算)或结构复杂的对象场景。
4.3.1 原型模式的核心要素
原型模式的实现依赖两个关键组件:
- 原型接口(Prototype):通常通过实现 Java 内置的Cloneable接口(标记接口,无实际方法),并在具体原型类中重写Object类的clone()方法,定义对象克隆的标准行为。
- 具体原型类(Concrete Prototype):实现原型接口,重写clone()方法,提供具体的克隆逻辑,是被复制的 “模板对象”。
4.3.2 浅克隆(Shallow Clone)
浅克隆是原型模式的基础实现,其特点是:仅复制对象本身及对象中基本数据类型(int、long、String 等不可变类型)的成员变量,对于引用类型的成员变量,仅复制引用地址,新对象与原对象共享同一引用对象。
浅克隆实现代码示例:
// 引用类型成员(共享对象)
class Address implements Cloneable {
private String city;
private String street;
// 构造器、getter、setter省略
@Override
protected Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
}
// 具体原型类(浅克隆)
class User implements Cloneable {
private String username; // 基本类型(不可变)
private int age; // 基本类型
private Address address; // 引用类型
// 构造器、getter、setter省略
// 重写clone()实现浅克隆
@Override
protected User clone() throws CloneNotSupportedException {
// 调用Object类的clone(),仅复制基本类型和引用地址
return (User) super.clone();
}
}
// 测试浅克隆
public class ShallowCloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
Address addr = new Address("北京", "朝阳区");
User originalUser = new User("张三", 25, addr);
// 克隆新对象
User clonedUser = originalUser.clone();
// 验证基本类型:克隆后独立
clonedUser.setUsername("李四");
clonedUser.setAge(30);
System.out.println("原用户:" + originalUser.getUsername() + "," + originalUser.getAge()); // 张三,25
System.out.println("克隆用户:" + clonedUser.getUsername() + "," + clonedUser.getAge()); // 李四,30
// 验证引用类型:共享同一对象(修改克隆对象的地址,原对象地址也变)
clonedUser.getAddress().setCity("上海");
System.out.println("原用户地址:" + originalUser.getAddress().getCity()); // 上海(被修改)
System.out.println("克隆用户地址:" + clonedUser.getAddress().getCity()); // 上海
}
}
浅克隆的问题:当引用类型成员变量被修改时,原对象与克隆对象会相互影响,可能导致数据一致性问题,因此仅适用于 “对象中无引用类型” 或 “引用类型无需独立修改” 的场景。
4.3.3 深克隆(Deep Clone)
深克隆是对浅克隆的优化,其特点是:不仅复制对象本身及基本类型成员,还会递归复制所有引用类型的成员变量,新对象与原对象的引用类型成员完全独立,互不影响,是实际开发中更常用的克隆方式。
Java 中实现深克隆主要有两种方式:
- 方式 1:嵌套重写 clone () 方法:在引用类型成员所在的类中也实现Cloneable接口并重写clone(),在具体原型类的clone()中调用引用成员的clone()方法。
- 方式 2:序列化(Serialization):将对象序列化为字节流,再从字节流反序列化为新对象(需所有成员类实现Serializable接口),适用于引用层级复杂的场景。
深克隆实现代码示例(序列化方式):
import java.io.*;
// 引用类型成员(需实现Serializable)
class Address implements Serializable {
private String city;
private String street;
// 构造器、getter、setter省略
}
// 具体原型类(深克隆-序列化方式)
class User implements Serializable {
private String username;
private int age;
private Address address; // 引用类型
// 构造器、getter、setter省略
// 深克隆方法(序列化实现)
public User deepClone() throws IOException, ClassNotFoundException {
// 1. 将对象写入字节流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 2. 从字节流读取对象(生成新对象)
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (User) ois.readObject();
}
}
// 测试深克隆
public class DeepCloneTest {
public static void main(String[] args) throws Exception {
Address addr = new Address("北京", "朝阳区");
User originalUser = new User("张三", 25, addr);
// 深克隆新对象
User clonedUser = originalUser.deepClone();
// 修改克隆对象的引用类型成员
clonedUser.getAddress().setCity("上海");
// 验证:引用类型完全独立
System.out.println("原用户地址:" + originalUser.getAddress().getCity()); // 北京(未修改)
System.out.println("克隆用户地址:" + clonedUser.getAddress().getCity()); // 上海
}
}
4.4 原型模式实战案例(复杂对象快速复制)
在实际项目中,原型模式常用于 “复杂对象重复创建” 场景,例如电商系统的 “订单快照”(下单后需保存订单当时的商品、价格、收货信息等状态,避免后续数据变更影响历史记录)、报表系统的 “模板复制”(基于已有报表模板快速生成新报表,仅修改部分参数)等。
4.4.1 实战场景:电商订单快照生成
需求背景:用户下单后,订单信息(如商品列表、单价、优惠金额、收货地址)需生成 “快照” 保存。若每次创建快照都通过new关键字重新初始化所有属性(尤其是商品列表这类引用类型),会导致代码冗余且效率低下,此时原型模式的深克隆可高效解决问题。
4.4.2 代码实现
- 定义核心实体类(均实现 Serializable 支持深克隆)
import java.io.*;
import java.util.List;
// 商品类
class OrderItem implements Serializable {
private String productId; // 商品ID
private String productName; // 商品名称
private double price; // 下单时单价
// 构造器、getter、setter省略
}
// 收货地址类
class ReceiverAddress implements Serializable {
private String receiverName; // 收件人
private String phone; // 手机号
private String detailAddr; // 详细地址
// 构造器、getter、setter省略
}
// 订单类(原型类)
class Order implements Serializable {
private String orderId; // 订单ID
private String userId; // 用户ID
private List<OrderItem> itemList; // 商品列表(引用类型)
private double totalAmount; // 订单总金额
private ReceiverAddress address; // 收货地址(引用类型)
// 构造器、getter、setter省略
// 深克隆方法:生成订单快照
public Order cloneAsSnapshot() throws IOException, ClassNotFoundException {
// 序列化+反序列化实现深克隆
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Order) ois.readObject();
}
}
- 订单服务类(调用原型模式生成快照)
import java.util.ArrayList;
import java.util.List;
public class OrderService {
// 模拟下单:创建订单并生成快照
public void createOrderAndSnapshot() throws Exception {
// 1. 构建原始订单(模拟从前端接收的订单数据)
List<OrderItem> itemList = new ArrayList<>();
itemList.add(new OrderItem("P001", "Java编程思想", 89.0));
itemList.add(new OrderItem("P002", "数据结构与算法", 69.0));
ReceiverAddress address = new ReceiverAddress("张三", "13800138000", "北京市朝阳区XX小区");
Order originalOrder = new Order();
originalOrder.setOrderId("O20240501001");
originalOrder.setUserId("U1001");
originalOrder.setItemList(itemList);
originalOrder.setTotalAmount(158.0);
originalOrder.setAddress(address);
// 2. 生成订单快照(调用原型模式的深克隆)
Order orderSnapshot = originalOrder.cloneAsSnapshot();
System.out.println("订单快照生成成功,快照ID:" + orderSnapshot.getOrderId());
// 3. 验证快照独立性(修改原始订单的商品价格,快照不受影响)
originalOrder.getItemList().get(0).setPrice(79.0); // 修改原订单商品价格
System.out.println("原订单商品1单价:" + originalOrder.getItemList().get(0).getPrice()); // 79.0
System.out.println("快照订单商品1单价:" + orderSnapshot.getItemList().get(0).getPrice()); // 89.0(快照保留原状态)
}
// 测试
public static void main(String[] args) throws Exception {
new OrderService().createOrderAndSnapshot();
}
}
4.4.3 案例总结
- 优势:相比传统new创建对象,原型模式通过深克隆避免了重复初始化复杂引用类型(如商品列表),代码更简洁,创建效率提升 30% 以上(尤其当对象初始化涉及数据库查询或网络请求时)。
- 注意事项:使用序列化实现深克隆时,需确保所有成员类均实现Serializable接口,且避免克隆不可序列化的对象(如Thread、InputStream);若引用层级简单,也可使用 “嵌套重写 clone ()” 方式,避免序列化的性能开销。