java设计模式[2]之创建型模式

文章目录

  • [一 创建型模式](#一 创建型模式)
    • [1.1 单例模式的设计与实现](#1.1 单例模式的设计与实现)
      • [1.1.1 饿汉式模式](#1.1.1 饿汉式模式)
      • [1.1.2 懒汉式单例模式](#1.1.2 懒汉式单例模式)
      • [1.1.3 懒汉式单例模式完善](#1.1.3 懒汉式单例模式完善)
      • [1.1.4 双重检测锁式](#1.1.4 双重检测锁式)
        • [1.1.4.1 volatile关键字](#1.1.4.1 volatile关键字)
        • [1.1.4.2 在双重检查锁定中的作用](#1.1.4.2 在双重检查锁定中的作用)
      • [1.1.5 静态内部类式单例模式](#1.1.5 静态内部类式单例模式)
      • [1.1.6 枚举式单例模式](#1.1.6 枚举式单例模式)
      • [1.1.7 反射暴力破解解决方案](#1.1.7 反射暴力破解解决方案)
      • [1.1.8 序列化和反序列化的解决方案](#1.1.8 序列化和反序列化的解决方案)
    • [1.2 单例模式的经典应用](#1.2 单例模式的经典应用)
    • [1.3 单例模式的业务实现](#1.3 单例模式的业务实现)
    • [1.4 工厂模式](#1.4 工厂模式)
      • [1.4.1 工厂模式的设计与实现](#1.4.1 工厂模式的设计与实现)
      • [1.4.2 简单工厂模式](#1.4.2 简单工厂模式)
      • [1.4.3 工厂方法模式](#1.4.3 工厂方法模式)
      • [1.4.4 抽象工厂模式](#1.4.4 抽象工厂模式)
      • [1.4.5 三者对比](#1.4.5 三者对比)
      • [1.4.6 工厂模式的经典应用](#1.4.6 工厂模式的经典应用)
      • [1.4.7 工厂模式的业务实现](#1.4.7 工厂模式的业务实现)
        • [1.4.7.1 简单工厂模式](#1.4.7.1 简单工厂模式)
        • [1.4.7.2 工厂方法模式](#1.4.7.2 工厂方法模式)
        • [1.4.7.3 抽象工厂模式](#1.4.7.3 抽象工厂模式)
    • [1.5 建造者模式](#1.5 建造者模式)
      • [1.5.1 建造者模式的设计与实现](#1.5.1 建造者模式的设计与实现)
      • [1.5.2 适用场景](#1.5.2 适用场景)
      • [1.5.3 优缺点](#1.5.3 优缺点)
      • [1.5.4 代码示例](#1.5.4 代码示例)
      • [1.5.5 建造者模式经典应用](#1.5.5 建造者模式经典应用)
      • [1.5.6 建造者模式和工厂模式的区别](#1.5.6 建造者模式和工厂模式的区别)
    • [1.6 原型模式](#1.6 原型模式)
      • [1.6.1 原型模式的设计与实现](#1.6.1 原型模式的设计与实现)
      • [1.6.2 浅克隆方式](#1.6.2 浅克隆方式)
      • [1.6.3 深克隆方式](#1.6.3 深克隆方式)
      • [1.6.4 序列化方式深克隆](#1.6.4 序列化方式深克隆)
        • [1.6.4.1 序列化深拷贝的原理](#1.6.4.1 序列化深拷贝的原理)
        • [1.6.4.2 适用前提](#1.6.4.2 适用前提)
        • [1.4.6.3 🔁 以 Java 原生序列化为例](#1.4.6.3 🔁 以 Java 原生序列化为例)
        • [1.4.6.4 📦 复杂引用关系的深拷贝](#1.4.6.4 📦 复杂引用关系的深拷贝)
        • [1.4.6.5 🧩 其他序列化方案对比](#1.4.6.5 🧩 其他序列化方案对比)
      • [1.6.5 原型模式的经典应用](#1.6.5 原型模式的经典应用)
      • [1.6.6 原型模式的业务实现](#1.6.6 原型模式的业务实现)

一 创建型模式

  • 创建型模式:对象的创建问题,封装复杂的创建过程,以及解耦对象的对象和代码的使用过程。
    • 单例模式:负责创建全局唯一的对象
    • 工厂模式:创建类型不同但相关的对象【父子类关系-继承接口】
    • 建造者模式:创建复杂对象
    • 原型模式:针对创建成本较大的对象,利用对已有对象的复制方式进行创建,达到节省创建时间的目录。

1.1 单例模式的设计与实现

  • 单例模式的核心是保证一个类只有一个实例,并且提供一个访问实例的全局访问点。
  • 单例模式可以分为5种实现方式。
实现方式 优缺点
饿汉式 线程安全,调用效率高,不能延迟加载
懒汉式 线程安全,调用效率不高,能延迟加载
双重检测锁式 在懒汉式的基础上解决并发问题
静态内部类式 线程安全,资源利用率高,可以延时加载
枚举单例 线程安全,调用效率高,不能延迟加载

1.1.1 饿汉式模式

  • 饿汉式单例模式:类加载的时候立即实例化对象,实现的步骤是先私有化构造方法,对外提供唯一的静态入口方法。
java 复制代码
package com.yang.designmode.singleton;

/**
 * 单例:饿汉式模式
 * 线程安全,单例对象在类装载的时候创建,只创建一次,所以是线程安全的,如果后面没有使用,会造成资源浪费
 * 唯一的对象:防止别人通过new关键字创建对象
 *  需要提供私有化所有的构造器
 * 提供全局的访问点:提供一个共有的static方法 返回单例对象
 */
public class HungrySingleton {

    private static final HungrySingleton instance=new HungrySingleton();

    //私有化构造方法
    private HungrySingleton() {

    }
    //对外提供共有方法
    public static HungrySingleton getInstance(){
        return instance;
    }
}
java 复制代码
package com.yang.designmode.singleton;

public class HungrySingletonTest {
    public static void main(String[] args) {
        HungrySingleton instance_0 = HungrySingleton.getInstance();
        HungrySingleton instance_1 = HungrySingleton.getInstance();
        System.out.println(instance_0);
        System.out.println(instance_1);
        System.out.println(instance_0==instance_1);
    }
}

1.1.2 懒汉式单例模式

java 复制代码
package com.yang.designmode.singleton;

/**
 * 懒汉式单例:线程不安全 在需要使用时加载对应的单例对象
 */
public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {

    }

    public static LazySingleton getInstance() throws InterruptedException {
        //通过构造器完成对象的创建
        if (instance == null) {
            Thread.sleep(10);
            instance = new LazySingleton();
        }
        return instance;
    }
}
java 复制代码
package com.yang.designmode.singleton;

public class LazySingletonTest {
    public static void main(String[] args) {
        System.out.println("懒汉式单例并发场景测试");
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                try {
                    System.out.println(LazySingleton.getInstance());
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }).start();
        }
    }
}
  • 线程不安全,初级创建在并发场景下可能出现创建多个实例的问题。
bash 复制代码
懒汉式单例并发场景测试
com.yang.designmode.singleton.LazySingleton@210b77ce
com.yang.designmode.singleton.LazySingleton@5359f47c
com.yang.designmode.singleton.LazySingleton@604f3632
com.yang.designmode.singleton.LazySingleton@7d70ea4f
com.yang.designmode.singleton.LazySingleton@255d3263
com.yang.designmode.singleton.LazySingleton@604f3632
com.yang.designmode.singleton.LazySingleton@9a67d48
com.yang.designmode.singleton.LazySingleton@48fc694
com.yang.designmode.singleton.LazySingleton@19eacfad
com.yang.designmode.singleton.LazySingleton@44ea945f
com.yang.designmode.singleton.LazySingleton@56c601fe
com.yang.designmode.singleton.LazySingleton@45534694
com.yang.designmode.singleton.LazySingleton@4594d87
com.yang.designmode.singleton.LazySingleton@45534694
com.yang.designmode.singleton.LazySingleton@4594d87
com.yang.designmode.singleton.LazySingleton@412be3e4

1.1.3 懒汉式单例模式完善

  • 在创建对象的方法中添加synchronized锁,保证创建的唯一性。
java 复制代码
public static synchronized LazySingleton getInstance() throws InterruptedException {
    //通过构造器完成对象的创建
    if (instance == null) {
        Thread.sleep(10);
        instance = new LazySingleton();
    }
    return instance;
}

1.1.4 双重检测锁式

java 复制代码
package com.yang.designmode.singleton;

/**
 * 双重检查锁定(Double-Check Locking)实现的单例模式。
 * 保证了多线程环境下的单例实例创建,并且只进行一次同步操作,提高性能。
 */
public class DoubleCheckSingleton {

    /**
     * 使用 volatile 关键字确保多线程环境下的可见性和禁止指令重排序。
     * 保证在所有线程中该变量的值是一致的。
     */
    private static volatile DoubleCheckSingleton instance;

    /**
     * 私有构造函数,防止外部通过 new 创建实例。
     */
    private DoubleCheckSingleton(){}

    /**
     * 获取单例对象的方法。
     * 使用双重检查锁定机制来延迟初始化单例对象。
     *
     * @return 单例对象
     */
    public static DoubleCheckSingleton getInstance(){
        // 第一次检查:如果 instance 已经被初始化,则直接返回,避免不必要的同步开销
        if(instance == null){
            // 同步类锁,保证只有一个线程进入代码块
            synchronized (DoubleCheckSingleton.class){
                // 第二次检查:确保只有一个实例被创建
                if(instance == null){
                    // 创建单例对象
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}
1.1.4.1 volatile关键字
  • Java中,volatile是一个关键字,用于修饰变量,确保多线程环境下的可见性和禁止指令重排序。
java 复制代码
private static volatile DoubleCheckSingleton instance;
  • ✅ 保证可见性(Visibility)

    • 在多线程环境中,每个线程可能会有自己的本地缓存(CPU 缓存),不加 volatile 时,读写操作可能只发生在本地缓存中。
    • 使用 volatile 后,每次读取都会从主内存中获取最新值,每次写入也会立即刷新到主内存中,从而保证多个线程看到的是同一个值。
  • ✅ 禁止指令重排序(Instruction Reordering)

    • 在创建对象时,JVM 可能会对指令进行优化重排,例如:
    java 复制代码
    instance = new DoubleCheckSingleton();

    实际上分为三步:

    1. 分配内存空间;
    2. 调用构造函数初始化对象;
    3. 将引用指向分配的内存地址。
    • 如果没有 volatile,有可能线程 A 执行完步骤 1 和 3,还没执行步骤 2,此时线程 B 检查 instance != null 并使用它,就会得到一个未完全初始化的对象,导致错误。使用 volatile 后,会插入内存屏障,防止上述重排序问题。

1.1.4.2 在双重检查锁定中的作用
  • 在双重检查锁定(Double-Checked Locking)中
java 复制代码
public static DoubleCheckSingleton getInstance(){
    if(instance == null){
        synchronized (DoubleCheckSingleton.class){
            if(instance == null){
                instance = new DoubleCheckSingleton();
            }
        }
    }
    return instance;
}
  • 第一次检查 if(instance == null) 是为了提高性能,避免每次都进入同步块。
  • 第二次检查是在同步块内再次确认是否已创建实例。
  • 如果 instance 没有被 volatile 修饰,就可能发生前面提到的"读取到未完全初始化的实例"的问题。
    因此,加上 volatile必须且关键的操作,否则双重检查锁定模式在多线程下是不安全的。

关键点 描述
volatile 关键字 保证变量在多线程之间的可见性和有序性
单例模式中为何需要 防止因指令重排导致线程拿到未初始化完成的对象
推荐保留 volatile 否则可能导致严重并发问题

1.1.5 静态内部类式单例模式

java 复制代码
package com.yang.designmode.singleton;

/**
 * 静态内部类单例模式
 */
public class StaticInnerSingleton {
    //私有化构造器
    private StaticInnerSingleton() {
    }

    //静态内部类
    private static class SingletonClassInstance {
        //声明一个外部类的静态成员变量,并且通过new  关键字创建对象和赋值
        private static final StaticInnerSingleton instance = new StaticInnerSingleton();
    }

    public static StaticInnerSingleton getInstance() {
        return SingletonClassInstance.instance;
    }
}
java 复制代码
public class StaticInnerSingletonTest {
    public static void main(String[] args) {
        System.out.println("静态内部类单例并发场景测试");
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                System.out.println(StaticInnerSingleton.getInstance());
            }).start();
        }
    }
}

1.1.6 枚举式单例模式

  • 枚举的实例化由JVM控制,在类加载完成就完成了没常量的初始化。
  • 枚举量默认被public static final修饰,确保实例的唯一和不可变。
java 复制代码
package com.yang.designmode.singleton;

public enum EnumSingleton {

    INSTANCE;

    private int value;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}
java 复制代码
package com.yang.designmode.singleton;

public class EnumSingletonTest {
    public static void main(String[] args) {
        for (int i=0;i<10;i++){
            new Thread(()->{
                System.out.println(EnumSingleton.INSTANCE);
            }).start();
        }
        System.out.println("===");
        EnumSingleton instance_1 = EnumSingleton.INSTANCE;
        instance_1.setValue(20);
        System.out.println(instance_1.getValue());
        EnumSingleton instance_2 = EnumSingleton.INSTANCE;
        System.out.println(instance_2.getValue());
        System.out.println(instance_1 == instance_2);
    }
}

1.1.7 反射暴力破解解决方案

  • 在Java中,反射机制可以访问并修改类的私有构造函数、字段和方法,这可能导致单例模式被破坏。为了解决这个问题,可以在构造函数中添加一个检查逻辑,如果实例已经被创建,则抛出异常以防止通过反射创建新的实例。

在私有构造函数中添加一个检查逻辑:

  • 如果instance不为 null,说明实例已经被创建。
  • 抛出运行时异常,阻止通过反射创建新的实例。
java 复制代码
private DoubleCheckSingletonDestory(){
    if(instance != null){
        // 说明有人想要破坏单例特性,创建多个实例
        throw new RuntimeException(DoubleCheckSingletonDestory.class + " 单例模式不允许创建多个实例");
    }
}
  • if (instance != null):检查是否已经存在一个实例。
  • throw new RuntimeException(...):如果实例已经存在,抛出异常,阻止创建新的实例。

  • 正常情况下,第一次调用 getInstance() 会成功创建实例。
  • 如果尝试通过反射调用私有构造函数来创建新实例,将会触发异常,从而保护单例模式不被破坏。

main 方法中,尝试通过反射创建实例会抛出异常:

java 复制代码
public static void main(String[] args) throws Exception {
    // 正常创建单例对象
    DoubleCheckSingletonDestory instance_1 = DoubleCheckSingletonDestory.getInstance();
    System.out.println(instance_1);

    // 通过反射创建单例对象
    Class<DoubleCheckSingletonDestory> doubleCheckSingletonDestoryClass = DoubleCheckSingletonDestory.class;
    Constructor<DoubleCheckSingletonDestory> declaredConstructor = doubleCheckSingletonDestoryClass.getDeclaredConstructor(null);
    // 放开私有的访问权限
    declaredConstructor.setAccessible(true);
    try {
        DoubleCheckSingletonDestory instance_2 = declaredConstructor.newInstance(null);
        System.out.println(instance_2);
    } catch (Exception e) {
        System.out.println("反射创建实例失败: " + e.getMessage());
    }
}
  • declaredConstructor.setAccessible(true):允许访问私有构造函数。
  • declaredConstructor.newInstance(null):尝试通过反射创建实例,但由于构造函数中的检查逻辑,最终会抛出异常。

  • 完整代码
java 复制代码
package com.yang.designmode.singleton;

import java.lang.reflect.Constructor;

/**
 * 双重检查锁定(Double-Check Locking)实现的单例模式。
 * 保证了多线程环境下的单例实例创建,并且只进行一次同步操作,提高性能。
 */
public class DoubleCheckSingletonDestory {

    /**
     * 使用 volatile 关键字确保多线程环境下的可见性和禁止指令重排序。
     * 保证在所有线程中该变量的值是一致的。
     */
    private static volatile DoubleCheckSingletonDestory instance;

    /**
     * 私有构造函数,防止外部通过 new 创建实例。
     */
    private DoubleCheckSingletonDestory(){
        if(instance!=null){
            //说明有人想要破坏单例特性,创建多个实例
            throw new RuntimeException(DoubleCheckSingletonDestory.class+"单例模式不允许创建多个实例");
        }
    }

    /**
     * 获取单例对象的方法。
     * 使用双重检查锁定机制来延迟初始化单例对象。
     *
     * @return 单例对象
     */
    public static DoubleCheckSingletonDestory getInstance(){
        // 第一次检查:如果 instance 已经被初始化,则直接返回,避免不必要的同步开销
        if(instance == null){
            // 同步类锁,保证只有一个线程进入代码块
            synchronized (DoubleCheckSingletonDestory.class){
                // 第二次检查:确保只有一个实例被创建
                if(instance == null){
                    // 创建单例对象
                    //潜在的安全问题 线程安全:原子性、可见性、顺序性
                    /*
                      1. 分配内存空间
                      2. 初始化对象
                      3. 将 instance 指向分配的内存地址
                     */
                    instance = new DoubleCheckSingletonDestory();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) throws Exception {
        // 正常创建单例对象
        DoubleCheckSingletonDestory instance_1 = DoubleCheckSingletonDestory.getInstance();
        System.out.println(instance_1);

        //通过反射创建单例对象
        Class<DoubleCheckSingletonDestory> doubleCheckSingletonDestoryClass= DoubleCheckSingletonDestory.class;
        Constructor<DoubleCheckSingletonDestory> declaredConstructor = doubleCheckSingletonDestoryClass.getDeclaredConstructor(null);
        // 放开私有的访问权限
        declaredConstructor.setAccessible(true);
        DoubleCheckSingletonDestory instance_2 = declaredConstructor.newInstance(null);
        System.out.println(instance_2);
    }
}

1.1.8 序列化和反序列化的解决方案

  • 通过序列化和反序列化破坏单例模式的案例
java 复制代码
import java.io.*;
/**
 * 双重检查锁定(Double-Check Locking)实现的单例模式。
 * 保证了多线程环境下的单例实例创建,并且只进行一次同步操作,提高性能。
 */
public class DoubleCheckSingletonSerializable implements Serializable {

    /**
     * 使用 volatile 关键字确保多线程环境下的可见性和禁止指令重排序。
     * 保证在所有线程中该变量的值是一致的。
     */
    private static volatile DoubleCheckSingletonSerializable instance;

    /**
     * 私有构造函数,防止外部通过 new 创建实例。
     */
    private DoubleCheckSingletonSerializable(){
        if(instance!=null){
            //说明有人想要破坏单例特性,创建多个实例
            throw new RuntimeException(DoubleCheckSingletonSerializable.class+"单例模式不允许创建多个实例");
        }
    }

    /**
     * 获取单例对象的方法。
     * 使用双重检查锁定机制来延迟初始化单例对象。
     *
     * @return 单例对象
     */
    public static DoubleCheckSingletonSerializable getInstance(){
        // 第一次检查:如果 instance 已经被初始化,则直接返回,避免不必要的同步开销
        if(instance == null){
            // 同步类锁,保证只有一个线程进入代码块
            synchronized (DoubleCheckSingletonSerializable.class){
                // 第二次检查:确保只有一个实例被创建
                if(instance == null){
                    // 创建单例对象
                    //潜在的安全问题 线程安全:原子性、可见性、顺序性
                    /*
                      1. 分配内存空间
                      2. 初始化对象
                      3. 将 instance 指向分配的内存地址
                     */
                    instance = new DoubleCheckSingletonSerializable();
                }
            }
        }
        return instance;
    }
    
    
    public static void main(String[] args) throws Exception {
        // 正常创建单例对象
        DoubleCheckSingletonSerializable instance_1 = DoubleCheckSingletonSerializable.getInstance();
        System.out.println(instance_1);

        //通过序列化和反序列化方式破坏单例
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("./c.txt"));
        oos.writeObject(instance_1);
        oos.flush();
        oos.close();
        //通过反序列化方式读取
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./c.txt"));
        DoubleCheckSingletonSerializable instance_2 = (DoubleCheckSingletonSerializable) ois.readObject();
        ois.close();

        System.out.println("instance_1 = "+instance_1);
        System.out.println("instance_2 = "+instance_2);
    }
}
  • 返回不同的对象。
bash 复制代码
instance_1 = com.yang.designmode.singleton.DoubleCheckSingletonSerializable@eed1f14
instance_2 = com.yang.designmode.singleton.DoubleCheckSingletonSerializable@4ccabbaa

  • 重写readResolve方法 在反序列化时候调用,不会创建新的对象,返回该方法的对象。
java 复制代码
import java.io.*;

/**
 * 双重检查锁定(Double-Check Locking)实现的单例模式。
 * 保证了多线程环境下的单例实例创建,并且只进行一次同步操作,提高性能。
 */
public class DoubleCheckSingletonSerializable implements Serializable {

    /**
     * 使用 volatile 关键字确保多线程环境下的可见性和禁止指令重排序。
     * 保证在所有线程中该变量的值是一致的。
     */
    private static volatile DoubleCheckSingletonSerializable instance;

    /**
     * 私有构造函数,防止外部通过 new 创建实例。
     */
    private DoubleCheckSingletonSerializable(){
        if(instance!=null){
            //说明有人想要破坏单例特性,创建多个实例
            throw new RuntimeException(DoubleCheckSingletonSerializable.class+"单例模式不允许创建多个实例");
        }
    }

    /**
     * 获取单例对象的方法。
     * 使用双重检查锁定机制来延迟初始化单例对象。
     *
     * @return 单例对象
     */
    public static DoubleCheckSingletonSerializable getInstance(){
        // 第一次检查:如果 instance 已经被初始化,则直接返回,避免不必要的同步开销
        if(instance == null){
            // 同步类锁,保证只有一个线程进入代码块
            synchronized (DoubleCheckSingletonSerializable.class){
                // 第二次检查:确保只有一个实例被创建
                if(instance == null){
                    // 创建单例对象
                    //潜在的安全问题 线程安全:原子性、可见性、顺序性
                    /*
                      1. 分配内存空间
                      2. 初始化对象
                      3. 将 instance 指向分配的内存地址
                     */
                    instance = new DoubleCheckSingletonSerializable();
                }
            }
        }
        return instance;
    }

    /**
     * 重写readResolve方法 在反序列化时候调用,不会创建新的对象,返回该方法的对象
     * @return
     * @throws ObjectStreamException
     */
    public Object readResolve() throws ObjectStreamException {
        return getInstance();
    }

    public static void main(String[] args) throws Exception {
        // 正常创建单例对象
        DoubleCheckSingletonSerializable instance_1 = DoubleCheckSingletonSerializable.getInstance();
        System.out.println(instance_1);

        //通过序列化和反序列化方式破坏单例
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("./c.txt"));
        oos.writeObject(instance_1);
        oos.flush();
        oos.close();
        //通过反序列化方式读取
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./c.txt"));
        DoubleCheckSingletonSerializable instance_2 = (DoubleCheckSingletonSerializable) ois.readObject();
        ois.close();

        System.out.println("instance_1 = "+instance_1);
        System.out.println("instance_2 = "+instance_2);
    }
}

  • 在DoubleCheckSingletonSerializable.java 中,readResolve() 方法返回 getInstance() 和直接返回 instance 的确存区别:
  1. 返回 getInstance():调用的是单例类的公共静态方法 getInstance()。保证了通过反序列化获取的对象仍然是全局唯一的单例对象。如果将来对 getInstance() 方法做了增强(比如增加新的初始化逻辑、日志记录等),readResolve() 会自动继承这些行为。更加符合面向对象设计原则,避免暴露内部变量。
  2. 直接返回 instance:是对私有静态变量 instance 的直接访问。在当前上下文中,虽然也能得到正确的单例对象,但跳过了 getInstance() 方法的逻辑。如果未来 getInstance() 方法中增加了额外处理逻辑,则不会被触发,可能造成不一致的行为。

  • ✅ 推荐做法:使用 return getInstance(); 是更安全和规范的做法。它确保了即使 getInstance() 方法后续发生变化,反序列化过程也能保持一致性,并遵循单例模式的设计意图。

1.2 单例模式的经典应用

  • 单例模式的经典应用:
  1. Spring中的Bean对象,默认是单例模式。
  2. 相关工厂对象都是单例模式,如:Mybatis中的SqlSessionFactory,Spring中的BeanFactory
  3. 保存相关配置信息的对象,如:Mybatis中的Configuration对象,SpringBoot中的各个xxAutoConfiguration对象。
  4. 应用程序的日志位置,一般都会通过单例实现。
  5. 数据库连接池的设计

1.3 单例模式的业务实现

  • 适合单例模式的应用场景:
  1. 配置管理模块:保证系统中只有一个配置管理实例,可以统一管理系统的配置信息。
  2. 日志管理模块:保证系统中只有一个日志管理实例,确保日志记录的一致性和准确性。
  3. 缓存管理模块:保证系统中只有一个缓存管理实例,提高系统的性能和效率。
  4. 数据库连接模块:保证系统中只有一个数据库连接池实例,避免数据库连接资源的浪费和冲突。

  • 日志管理案例代码
java 复制代码
package com.yang.designmode.singleton.business;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

/**
 * 日志类 单例模式 饿汉式
 */
public class Logger4 {
    //实现日志的输出
    private FileWriter fileWriter;

    private static Logger4 logger4 = new Logger4();

    //实现日志输出
    private Logger4() {
        File file = new File("./log.txt");
        try {
            FileWriter writer = new FileWriter(file, true);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static Logger4 getInstance() {
        return logger4;
    }

    /**
     * 对外输出日志的方法
     * @param message
     */
    public void log(String message) throws IOException {
            fileWriter.write(message);
    }
}
java 复制代码
import java.io.IOException;

public class OrderController {
    private Logger1 logger1=new Logger1();
    private Logger4 logger4=Logger4.getInstance(); // 饿汉式单例模式

    public void createOrder(){
        try {
            logger1.log("创建订单的处理");
            logger4.log("创建订单的处理");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

1.4 工厂模式

  • 工厂模式帮助我们创建对象,不用自己创建,即封装对象的创建过程,将对象的创建和使用分离,减低代码的复杂度。

1.4.1 工厂模式的设计与实现

  • 工厂模式根据需要创建对象的复杂度可以分为简单工厂、工厂方法和抽象工厂。

1.4.2 简单工厂模式

  • 简单工厂模式(静态工厂方法):可以根据不同的参数返回不同的实例,简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
java 复制代码
/**
 * 工厂模式:
 *  简单工厂模式
 *  汽车工厂:BYD和Audi
 */
public class SimpleFactory {
    public static void main(String[] args) {
        CarFactory.createCar("Audi").run();
        CarFactory.createCar("BYD").run();
        CarFactory.createCar("Benz").run();
    }
}
interface Car {
    void run();
}

//定义Car的实现类
class Audi implements Car  {
    @Override
    public void run()
    {
        System.out.println("Audi is running...");
    }
}

class BYD implements Car  {
    @Override
    public void run()
    {
        System.out.println("BYD is running...");
    }
}

//创建一个对应的简单工厂类
class CarFactory {
    //生产汽车的方法
    public static Car createCar(String type) {
        if(type.equals("Audi")){
            return new Audi();
        }else if(type.equals("BYD")){
            return new BYD();
        }
        throw new RuntimeException("没有这种类型的汽车");
    }
}

1.4.3 工厂方法模式

java 复制代码
/**
 * 工厂方法模式
 */
public class FactoryMethod {
    public static void main(String[] args) {
        CarFactory  audiFactory = new AudiFactory();
        Car car = audiFactory.createCar();
        car.run();
        CarFactory  bydFactory = new BYDFactory();
        Car car1 = bydFactory.createCar();
        car1.run();
    }
}

interface Car {
    void run();
}

//定义Car的实现类
class Audi implements Car {
    @Override
    public void run() {
        System.out.println("Audi is running...");
    }
}

class BYD implements Car {
    @Override
    public void run() {
        System.out.println("BYD is running...");
    }
}

interface CarFactory {
    Car createCar();
}

class AudiFactory implements CarFactory {
    @Override
    public Car createCar() {
        return new Audi();
    }
}

class BYDFactory implements CarFactory {
    @Override
    public Car createCar() {
        return new BYD();
    }
}

  • 简单工厂和工厂方法的对比。
  1. 简单工厂只有一个工厂,而工厂方法有多个工厂。
  2. 简单工厂不支持拓展,而工厂方法支持拓展,拓展的方法就是添加对应的工厂类。
  3. 简单工厂代码复杂度低,工厂方法代码复杂度高。

1.4.4 抽象工厂模式

java 复制代码
package com.yang.designmode.factory.AbstractFactory;

public class AbstractFactory {

    public static void main(String[] args) {
        Car car = new LuxuryEngineCarFactory().createCar();
        Engine engine = new LuxuryEngineCarFactory().createEngine();
        car.run();
        engine.run();

    }
    //抽象工厂
    public static interface AbstractComponentFactory {
        Car createCar();
        Engine createEngine();
    }

    public static class LuxuryEngineCarFactory  implements AbstractComponentFactory {
        @Override
        public Car createCar() {
            return new BydCarFactory().createCar();
        }
        @Override
        public Engine createEngine() {
            return new LuxuryEngineFactory().createEngine();
        }
    }

    public static class LowEngineCarFactory  implements AbstractComponentFactory {
        @Override
        public Car createCar() {
            return new AudiCarFactory().createCar();
        }
        @Override
        public Engine createEngine() {
            return new LowEngineFactory().createEngine();
        }
    }

    //汽车产品族
    public static interface Car {
        void run();
    }

    public static class Byd implements Car {
        @Override
        public void run() {
            System.out.println("比亚迪...");
        }
    }

    public static class Audi implements Car {
        @Override
        public void run() {
            System.out.println("奥迪...");
        }
    }

    public static interface CarFactory {
        Car createCar();
    }


    public static class AudiCarFactory implements CarFactory{
        public Car createCar() {
            return new Audi();
        }
    }
    public static class BydCarFactory implements CarFactory{
        public Car createCar() {
            return new Byd();
        }
    }

    //发动机产品族
    public static interface EngineFactory {
        Engine createEngine();
    }
    public static interface Engine {
        void run();
    }
    public static class LuxuryEngine implements Engine {
        @Override
        public void run() {
            System.out.println(" luxury engine...");
        }

    }
    public static class LowEngine implements Engine {
        @Override
        public void run() {
            System.out.println(" low engine...");
        }

    }

    public static class LuxuryEngineFactory implements EngineFactory{
        public Engine createEngine() {
            return new LuxuryEngine();
        }
    }

    public static class LowEngineFactory implements EngineFactory{
        public Engine createEngine() {
            return new LowEngine();
        }
    }

}

1.4.5 三者对比

  1. 简单工厂模式(静态工厂模式):虽然某种程度不符合设计原则,但实际使用最多。
  2. 工厂方法模式:不修改已有类的前提下,通过增加新的工厂类实现拓展。
  3. 抽象工厂模式:不可以增加产品,可以增加产品族。

1.4.6 工厂模式的经典应用

  1. Spring框架:
  • BeanFactory和ApplicationContexti:Spring的核心接口BeanFastory是一个工厂模式的实现,它负责创建和管理应用中的bean。而ApplicationContext是BeanFactory的子接口,提供更丰富的功能,如国际化支持、事件传播等。
  • FactoryBean:.Spring提供FactoryBean接口,允许用户定义如何创建对象,而不仅仅是简单地通过类的构造器来实例化。 FastoryBean提供了一种更灵活的方式来创建复杂对象。
  1. MyBatis框架:
  • SqlSessionFastory:MyBatis通过SqlsessionFactory来创建SqlSession实例,Sqlsession用于执行SQL命令、获取映射器实例和管理事务。SqlSessionFactory是一个工厂类,它遵循工厂模式的设计原则。
  1. Hibernate框架:
  • SessionFactory:在Hibernate中,SessionFactory是一个用于创建Session实例的工厂类。Session是Hibernate中用于执行数据库操作的主要接口,而SessionFactory则负责管理Session的创建和配置。
  1. 其他框架和API
  • 日志框架(如Log4j、SLE4J):提供工厂类或方法来创建日志记录器(Logger)实例。例如,在SLF4J中,LoggerFactory类用于创建Logger实例。
  • JDBC连接池:在JDBC连接池中,如Apache Commons DBCP、 C3P0等,都使用了工厂模式来管理数据库连接的创建、复用和销毁。
  • 依赖注入框架:除Spring之外,还有其他依赖注入框架(如Google Guice) 也使用了工厂模式来管理对象的创建和依赖注入。

1.4.7 工厂模式的业务实现

  • 工厂模式的业务实现我们可以根据工厂模式的分类来具体的分析
1.4.7.1 简单工厂模式
  • 应用场景:
    • 当需要创建的对象较少且不会频繁增加时,可以使用简单工厂模式。
    • 客户端只需要知道传入工厂类的参数,无需知道具体产品的类名。
  • 业务案例:
    • 在一个基于控制台的日志系统中,可以根据用户输入的日志级别(如INFO、 DEBUG、 ERROR) 来创建不同类型的日志记录器 (Logger) 对象。
    • 在一个图形界面应用程序中,可以根据用户的选择来创建不同种类的按钮(Button)对象。
1.4.7.2 工厂方法模式
  • 应用场景:
    • 当一个类不知道它所必须创建的对象的类时
    • 当一个类希望由它的子类来指定它所创建的对象时
    • 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化时。
  • 业务案例
    • 在一个文档处理系统中,可以定义---个Document接口,然后为不同类型的文档(如Word、PDF、 Text等)创建具体的类,并分别实现Document接口。同时,定义一个PocumentFactory接口,并为每种类型的文档创建一个实现了DocumgntFactoy接口的工厂类。在需要创建文档对象时,只需调用相应的工厂类即可。
1.4.7.3 抽象工厂模式
  • 应用场景
    • 当一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节时。
    • 当这个系统有多于一个的产品族,而系统只消费其中某一族的产品时。
    • 当同一个产品族中的多个对象被设计在一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。
  • 业务案例:在一个图形界面库中,可能需要创建不同风格的按钮(Button)和文本框(TextField)对象。每种风格(如Windows风格、Mac风格)都包含一套按钮和文本框的类。此时,可以使用抽象工厂模式来定义一个抽象工厂接口,该接口包含创建按钮和文本框的方法。然后,为每种风格创建一个实现了抽象工厂接口的工厂类。在需要创建特定风格的图形界面组件时,只需调用相应风格的工厂类即可。

1.5 建造者模式

1.5.1 建造者模式的设计与实现

  • 建造者模式(Builder Pattern)是一种创建型设计模式,用于将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。
  • 建造者模式适用于需要分步骤构建复杂对象的情况。它通过将构建过程封装在 Director 和 Builder 中,实现了高内聚低耦合的设计思想,是处理复杂对象创建的理想选择之一。

  • 建造者模式中主要的角色分类:
  1. Builder(建造者接口) :定义构建产品各个部分的方法(如 buildPartA(), buildPartB() 等)。
  2. ConcreteBuilder(具体建造者):实现 Builder 接口,提供具体的构建逻辑,并返回构建好的产品对象。
  3. Director(指挥者):负责使用 Builder 接口来定义构建的顺序,不关心具体实现细节。
  4. Product(产品角色):最终要构建的复杂对象,通常由多个部分组成。

1.5.2 适用场景

  • 当一个对象的构建过程复杂且需要多个步骤完成时。
  • 同样的构建过程需要产生不同的产品时。
  • 需要隔离产品的构造与表示,使客户端无需了解内部构建细节。

1.5.3 优缺点


优点

  • 将构建过程和产品表示解耦,提高扩展性。
  • 更好地控制构建过程,便于添加新的建造者类。
  • 可以逐步精细地构建对象,避免构造函数参数过多的问题。

缺点

  • 增加系统复杂度,需要额外定义多个类(Builder、ConcreteBuilder、Director)。
  • 如果产品差异较大,可能不适合使用该模式。

1.5.4 代码示例

java 复制代码
// Product:产品角色
class House {
    private String foundation;
    private String walls;
    private String roof;

    public void setFoundation(String foundation) {
        this.foundation = foundation;
    }

    public void setWalls(String walls) {
        this.walls = walls;
    }

    public void setRoof(String roof) {
        this.roof = roof;
    }

    @Override
    public String toString() {
        return "House{" +
                "foundation='" + foundation + '\'' +
                ", walls='" + walls + '\'' +
                ", roof='" + roof + '\'' +
                '}';
    }
}

// Builder:抽象建造者
interface HouseBuilder {
    void buildFoundation();
    void buildWalls();
    void buildRoof();
    House getHouse();
}

// ConcreteBuilder:具体建造者
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("Tile Roof");
    }

    @Override
    public House getHouse() {
        return house;
    }
}

// Director:指挥者
class Director {
    private HouseBuilder builder;

    public Director(HouseBuilder builder) {
        this.builder = builder;
    }

    public void constructHouse() {
        builder.buildFoundation();
        builder.buildWalls();
        builder.buildRoof();
    }
}

// 测试类
public class Client {
    public static void main(String[] args) {
        HouseBuilder builder = new ConcreteHouseBuilder();
        Director director = new Director(builder);
        
        director.constructHouse();
        House house = builder.getHouse();
        
        System.out.println(house.toString());
    }
}
  • 输出结果

    House{foundation='Concrete Foundation', walls='Brick Walls', roof='Tile Roof'}

1.5.5 建造者模式经典应用

  1. Guava中的缓存处理。
  2. MyBatis中的SqlSessionFactoryBuilder的处理。
  3. MyBatis中的MappedStatement的创建。

1.5.6 建造者模式和工厂模式的区别

  • 建造者模式和工厂模式都可以创建对象,它们创建对象的关注点不一样。工厂模式用来创建类型不同但有相关的对象(继承同一个父类或接口的一组子类),通过参数来决定创建哪种类型的对象。建造者模式用来创建同一种类型复杂对象,通过设置不同的可选参数来定制化创建不同的对象。
  • 现实案例去餐厅吃饭:
    • 工厂模式:炒饭,火锅,炒菜......
    • 建造车模式:火锅【不同的口味:麻辣、香辣、清淡......】

1.6 原型模式

1.6.1 原型模式的设计与实现

  • 原型模式(克隆模式):以一个对象为原型克隆出一个一模一样的对象,该对象的属性和原型对象一样。而且对原型对象没有任何影响。
  • 它的主要目的是通过已有的对象来创建新对象,而无需调用构造函数。
  • 核心思想:原型模式的核心在于clone()方法的使用,它允许你通过复制一个已有对象来创建新的对象实例。这在创建对象的成本较大时非常有用。
  • 原型模式的克隆模式分为两种:浅克隆(浅拷贝)和深克隆(深拷贝)。
原型模式 说明
浅克隆(浅拷贝) 只复制基本数据类型的值,对引用类型只复制引用地址
深克隆(深拷贝) 递归复制所有关联的对象,确保完全独立
  • 应用场景
    • 当系统需要动态加载类或者避免通过工厂方法频繁创建对象时。
    • 创建对象成本较高时(如数据库连接、大对象初始化等)。
    • 需要保证对象之间的隔离性,例如撤销/重做功能。

1.6.2 浅克隆方式

  • 被复制对象的所有变量都含有与原来对象相同的值,所有对其他对象的引用仍然指向原来的对象。换句话说,浅复制只复制所考虑对象,不复制它所引用的对象。Object类提供的方法clone只拷贝本对象对象内部的数组和引用对象都不拷贝,仍然指向原生对象的内部元素地址
  • 被克隆对象必须实现cloneable、Serializable接口。
java 复制代码
package com.yang.designmode.prototype;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Date;

public class UserShallowType implements Cloneable {
    private String name;
    private int age;
    private Date birthday;


    public UserShallowType() {
    }

    public UserShallowType(String name, int age, Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    public String toString() {
        return "User [name=" + name + ", age=" + age + ", birthday=" + birthday + "]";
    }



    @Override
    public UserShallowType clone() {
        try {
            UserShallowType clone = (UserShallowType) super.clone();
            // TODO: 复制此处的可变状态,这样此克隆就不能更改初始克隆的内部项
            return clone;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }

    public static void main(String[] args) {
        UserShallowType user = new UserShallowType();
        user.setName("yang");
        user.setAge(18);
        LocalDate localDate = LocalDate.parse("2000-01-01", DateTimeFormatter.ISO_LOCAL_DATE);
        user.setBirthday(java.sql.Date.valueOf(localDate));

        UserShallowType user1 = user.clone();
        user1.setName("yang1");

        System.out.println("源对象:" + user);
        System.out.println("浅克隆对象:" + user1);


        // 验证共享 birthday 的行为
        Date userBirthday = user.getBirthday();
        userBirthday.setTime(userBirthday.getTime() + 1000 * 60 * 60 * 24 * 1); // 加一天
        System.out.println("验证共享 birthday 的行为");
        System.out.println("源对象:" + user);
        System.out.println("浅克隆对象:" + user1);
    }
}
bash 复制代码
源对象:User [name=yang, age=18, birthday=2000-01-01]
浅克隆对象:User [name=yang1, age=18, birthday=2000-01-01]
验证共享 birthday 的行为
源对象:User [name=yang, age=18, birthday=2000-01-02]
浅克隆对象:User [name=yang1, age=18, birthday=2000-01-02]
  • 浅克隆的问题:只复制基本数据类型的值,对引用类型只复制引用地址。

1.6.3 深克隆方式

java 复制代码
package com.yang.designmode.prototype;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Date;

public class UserDeepType implements Cloneable {
    private String name;
    private int age;
    private Date birthday;


    public UserDeepType() {
    }

    public UserDeepType(String name, int age, Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    public String toString() {
        return "User [name=" + name + ", age=" + age + ", birthday=" + birthday + "]";
    }



    @Override
    public UserDeepType clone() {
        try {
            UserDeepType clone = (UserDeepType) super.clone();
            // 实现克隆对象属性的深拷贝
            clone.birthday = (Date) clone.birthday.clone();
            return clone;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }

    public static void main(String[] args) {
        UserDeepType user = new UserDeepType();
        user.setName("yang");
        user.setAge(18);
        LocalDate localDate = LocalDate.parse("2000-01-01", DateTimeFormatter.ISO_LOCAL_DATE);
        user.setBirthday(java.sql.Date.valueOf(localDate));

        UserDeepType user1 = user.clone();
        user1.setName("yang1");

        System.out.println("源对象:" + user);
        System.out.println("深克隆对象:" + user1);


        // 验证不共享 birthday 的行为
        Date userBirthday = user.getBirthday();
        userBirthday.setTime(userBirthday.getTime() + 1000 * 60 * 60 * 24 * 1); // 加一天
        System.out.println("验证不共享 birthday 的行为");
        System.out.println("源对象:" + user);
        System.out.println("深克隆对象:" + user1);
    }
}
bash 复制代码
源对象:User [name=yang, age=18, birthday=2000-01-01]
深克隆对象:User [name=yang1, age=18, birthday=2000-01-01]
验证共享 birthday 的行为
源对象:User [name=yang, age=18, birthday=2000-01-02]
深克隆对象:User [name=yang1, age=18, birthday=2000-01-01]

1.6.4 序列化方式深克隆

  • 在对象引用关系复杂的情况下,使用 clone() 方法实现深拷贝会变得非常繁琐,尤其是当对象中包含嵌套引用、循环引用或多层结构时。此时,通过序列化和反序列化的方式进行深拷贝是一种更为通用和稳定的解决方案。

1.6.4.1 序列化深拷贝的原理
  • 将对象 序列化为字节流 (或 JSON 字符串等),然后再 反序列化为新对象
  • 因为整个过程是"从无到有"的重建,所以新的对象与原对象完全独立,实现了真正的深拷贝。
1.6.4.2 适用前提
  • 对象及其所有引用的对象都必须实现 Serializable 接口(如果是 Java 原生序列化)。
  • 避免使用 transient 关键字修饰需要拷贝的字段。

1.4.6.3 🔁 以 Java 原生序列化为例
java 复制代码
package com.yang.designmode.prototype;

import java.io.*;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Date;

public class UserDeepTypeSerializable implements Cloneable,Serializable {
    private String name;
    private int age;
    private Date birthday;


    public UserDeepTypeSerializable() {
    }

    public UserDeepTypeSerializable(String name, int age, Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    public String toString() {
        return "User [name=" + name + ", age=" + age + ", birthday=" + birthday + "]";
    }


    @Override
    public UserDeepTypeSerializable clone() {
        try {
            UserDeepTypeSerializable clone = (UserDeepTypeSerializable) super.clone();
            // 实现克隆对象属性的深拷贝
            clone.birthday = (Date) clone.birthday.clone();
            return clone;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }

    public static void main(String[] args) throws Exception {
        UserDeepTypeSerializable user = new UserDeepTypeSerializable();
        user.setName("yang");
        user.setAge(18);
        LocalDate localDate = LocalDate.parse("2000-01-01", DateTimeFormatter.ISO_LOCAL_DATE);
        user.setBirthday(java.sql.Date.valueOf(localDate));

        System.out.println("源对象:" + user);

        //序列化
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(user);

        //反序列化
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        UserDeepTypeSerializable user_1 = (UserDeepTypeSerializable)ois.readObject();
        System.out.println("序列化深克隆对象:"+user_1);

        // 验证不共享 birthday 的行为
        Date userBirthday = user.getBirthday();
        userBirthday.setTime(userBirthday.getTime() + 1000 * 60 * 60 * 24 * 1); // 加一天
        System.out.println("验证不共享 birthday 的行为");
        System.out.println("源对象:" + user);
        System.out.println("深克隆对象:" + user_1);
    }
}
bash 复制代码
源对象:User [name=yang, age=18, birthday=2000-01-01]
序列化深克隆对象:User [name=yang, age=18, birthday=2000-01-01]
验证不共享 birthday 的行为
源对象:User [name=yang, age=18, birthday=2000-01-02]
深克隆对象:User [name=yang, age=18, birthday=2000-01-01]
1.4.6.4 📦 复杂引用关系的深拷贝
java 复制代码
// 定义类结构
import java.io.Serializable;

class Address implements Serializable {
    private String city;
    private String street;

    public Address(String city, String street) {
        this.city = city;
        this.street = street;
    }

    // Getter & Setter
}

class User implements Serializable {
    private String name;
    private Address address;

    public User(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    // Getter & Setter
}
java 复制代码
// 使用深拷贝工具
public class Client {
    public static void main(String[] args) {
        Address address = new Address("Beijing", "Chaoyang Road");
        User user = new User("Alice", address);

        User clonedUser = DeepCopyUtil.deepCopy(user);

        // 修改原对象属性
        user.getAddress().setCity("Shanghai");

        System.out.println(clonedUser.getAddress().getCity()); // 输出: Beijing(未受影响)
    }
}

1.4.6.5 🧩 其他序列化方案对比
方案 特点 适用场景
Java 原生序列化 简单,但性能较差,需实现 Serializable 快速原型、调试
JSON 序列化(如 Jackson、Gson) 可读性好,兼容性强 Web 服务、跨语言交互
Kryo / Hessian / Protobuf 高性能二进制序列化 大数据、高性能要求系统

1.6.5 原型模式的经典应用

  • 原型模式在Spring中主要体现在Bean的作用域管理上。Spring默认以单例模式(Singleton Scope)创建和管理Bean,但它也支持原型作用域 (Prototype Scope)。当Bean的作用域被设置为prototype时,Spring容器会对每次的getBean()请求都创建一个新的Bean实例,而不是返回同一个共享的实例。在需要每次请求都拥有独立对象实例的场景中非常有用,比如处理用户会话或请求时。
  • Apache Commons Lang库中的SerializationUtils方法也实现了原型模式,通过序列化和反序列化的方式实现对象的深拷贝。这种方法可以处理复杂的对象图,包括那些含有循环引用的对象。

在下面这些场景中可以考虑使用原型模式:

  • 高成本对象的创建:当对象的创建成本较高,例如涉及复杂的初始化过程、大量数据的加载或昂贵的I/O操作时,使用原型模式可以避免重复的创建过程,通过复制现有对象来快速生成新对象,从而提高性能。
  • 减少初始化时间:通过复制一个已经初始化的实例,可以快速生成新的实例,减少了初始化所需的时间。
  • 相似对象的创建:当系统中存在大量相似的对象,且对象之间的差异主要在于少数几个属性时,可以使用原型模式来复制基础对象,并根据需要修改这些属性,从而快速生成大量相似但不完全相同的对象。
  • 图形界面组件:在图形用户界面中,复杂的组件如表格、树等可能需要从磁盘或数据库加载大量数据。使用原型模式可以快速复制已有的组件实例,而无需重新加载数据。
  • 数据库连接池:数据库连接的创建和销毁成本较高,使用原型模式可以重用现有的连接对象,减少连接建立和关闭的开销。
  • 对象缓存:对于需要频繁访问但创建成本较高的对象,可以将其缓存起来,并在需要时通过复制缓存中的对象来快速获取新对象,从而减少创建新对象的次数。
  • 灵活配置:在复制过程中,可以对对象进行动态修改,以适应不同的使用场景或配置需求。这种灵活性使得原型模式在需要动态生成不同状态对象时非常有用。
  • 复杂构造函数:当对象的构造函数过于复杂,或者存在多个构造函数时,原型模式提供一种更灵活的创建方式。通过复制一个已经配置好的对象,可以避免调用复杂的构造函数。
  • 对象间的依赖:在某些情况下,对象的创建可能依赖于其他多个对象的状态或配置。使用原型模式可以简化依赖关系,通过复制正确配置的对象来快速生成新对象。

1.6.6 原型模式的业务实现

  • 在电商系统中为统计用户的喜好和当时的热点方向。在数据库中存储大约30万条所有关键词信息,帮助分析用户喜好,为决策提供支持,搜索关键信息包括关键词,搜索次数和时间戳等信息。
  • 系统A会把检索数据加载到内存中提高查询效率,存储在HashMap中;系统B会间隔统计分析搜索日志信息并更新到数据库中。

java 复制代码
import java.io.*;
import java.util.HashMap;
import java.util.List;

/**
 * 系统A要同步数据中的统计发数据
 * 保证数据的一致性
 */
public class ProtoType {
    //记录缓存 检索统计数据
    private HashMap<String, SearchWord> currentKeyWords = new HashMap<>();
    private long lastUpdateTime = -1;

    /**
     * 系统A需要调用同步的方法
     * 1. 根据时间戳查询出对应的统计数据
     * 2. 遍历每条统计数据
     * 3. 比较已经缓存的数据,存在就替换,不存在就删除
     * 存在的问题:更新的时刻,无法保证一致性
     */
    public void refresh() throws Exception {
        //记录新的数据
        HashMap<String, SearchWord> newKeyWords = (HashMap<String, SearchWord>) deeppClone(currentKeyWords);


        // 根据时间戳查询出对应的记录信息
        List<SearchWord> list = getSearchWords(lastUpdateTime);
        long maxNewUpdateTime = lastUpdateTime;
        if(list!=null){
            // 遍历每条统计数据
            for (SearchWord data : list) {
                if(data.getLastUpdateTime()>maxNewUpdateTime){
                    maxNewUpdateTime = data.getLastUpdateTime();
                }
                if (newKeyWords.containsKey(data.getKeyWord())) {
                    newKeyWords.replace(data.getKeyWord(), data);
                } else {
                    newKeyWords.put(data.getKeyWord(), data);
                }
            }
            lastUpdateTime=maxNewUpdateTime;
            currentKeyWords = newKeyWords;
        }
    }

    private List<SearchWord> getSearchWords(long lastUpdateTime){


        return null;
    }

    public Object deeppClone(Object obj) throws Exception {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(obj);

        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return ois.readObject();
    }
}
相关推荐
红石程序员1 分钟前
VSCode配置C++项目全攻略
开发语言·c++·visual studio
徐新帅1 分钟前
基于 C 语言的图书管理系统开发详解
c语言·开发语言·数据结构
Chase_______12 分钟前
静态变量详解(static variable)
java·开发语言·jvm
救救孩子把12 分钟前
如何在n8n中突破Python库限制,实现持久化虚拟环境自由调用
开发语言·python·n8n
厚衣服_314 分钟前
第15篇:数据库中间件高可用架构设计与容灾机制实现
java·数据库·中间件
勇闯IT30 分钟前
有多少小于当前数字的数字
java·数据结构·算法
小皮侠1 小时前
【算法篇】逐步理解动态规划模型6(回文串问题)
java·开发语言·算法·动态规划
勤奋的小王同学~1 小时前
(javaSE)抽象类和接口:抽象类概念语法和特性, 抽象类的作用;接口的概念 接口特性 实现多个接口 接口间的继承 Object类
java·开发语言
Ai财富密码2 小时前
【Linux教程】Linux 生存指南:掌握常用命令,避开致命误操作
java·服务器·前端
LUCIAZZZ2 小时前
项目拓展-Jol分析本地对象or缓存的内存占用
java·开发语言·jvm·数据库·缓存·springboot