每日一学:设计模式之原型模式

一、什么是原型模式?

原型模式创建型设计模式 ,核心思想:通过复制(拷贝)一个已存在的实例(原型),来创建新的对象 ,而不是通过 new 关键字重新创建。

核心特点

  1. 隐藏对象创建的复杂过程
  2. 性能比 new 创建对象更高(尤其是大对象 / 复杂初始化)
  3. 可以在运行时动态创建对象
  4. Java 实现核心:Cloneable 接口 + clone() 方法

二、Java 实现方式

Java 提供了原生支持,分两种拷贝

  • 浅拷贝(默认):只复制基本类型,引用类型共用同一个对象
  • 深拷贝:完全复制,包括引用类型的对象(相互独立)

1. 浅拷贝

步骤:

  1. 类实现 Cloneable 接口(标记接口,无方法)
  2. 重写 Object 类的 clone() 方法

代码示例:

java 复制代码
package prototype;

// 原型类:实现 Cloneable 接口
class User implements Cloneable {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 正常重写代码,但是返回的是Object,为了方便使用。我们自己重写下
//    @Override
//    protected Object clone() throws CloneNotSupportedException {
//        return super.clone();
//    }

    @Override
    protected User clone() {
        try{
            return (User) super.clone();
        }catch (CloneNotSupportedException e){
            throw new RuntimeException();
        }
    }

    @Override
    public String toString() {
        return "name:"+name+"-age:"+age;
    }
}

测试:

2、浅拷贝存在的问题:无法拷贝引用类型

举例

我们给User中再创建一个对象Role(省略get、set)

java 复制代码
public class Role {

    private String roleName;

    public Role(String roleName) {
        this.roleName = roleName;
    }
}

每个用户一个角色(省略get、set)

java 复制代码
// 原型类:实现 Cloneable 接口
class User implements Cloneable {
    private String name;
    private int age;

    private Role role;

    public User(String name, int age, Role role) {
        this.name = name;
        this.age = age;
        this.role = role;
    }

    @Override
    protected User clone() {
        try{
            return (User) super.clone();
        }catch (CloneNotSupportedException e){
            throw new RuntimeException();
        }
    }

    @Override
    public String toString() {
        return "name:"+name+"-age:"+age;
    }
}

测试结果

java 复制代码
package prototype;

public class Test {
    public static void main(String[] args) {
        // 1. 创建原型对象
        Role role = new Role("超级管理员");
        User prototype = new User("张三", 20,role);

        // 2. 拷贝新对象
        User cloneUser = prototype.clone();

        System.out.println(prototype);          // name:张三-age:20-roleName:超级管理员
        System.out.println(cloneUser);          // name:张三-age:20-roleName:超级管理员


        // !!!注意,这里我修改拷贝对象的角色
        cloneUser.getRole().setRoleName("普通用户");
        // 再来输出一次
        System.out.println(prototype);          // name:张三-age:20-roleName:普通用户
        System.out.println(cloneUser);          // name:张三-age:20-roleName:普通用户
    }
}

这里我们可以看到我修改的是拷贝对象,但是原对象也被修改了

为什么出现这样的结果

因为在我们使用浅拷贝时,拷贝的对象的引用类型实际指向的还是原对对象的引用类型的地址,他们共用这个引用类型,所以修改一个会导致都被修改

3、 深拷贝(解决引用类型共用问题)

浅拷贝缺陷 :如果对象里有引用类型(如对象、集合),拷贝后会共用同一个引用,修改一个会影响另一个。

深拷贝目标:所有层级都完全复制,互不影响

Java 深拷贝两种实现:

  1. 手动递归拷贝
  2. 序列化 / 反序列化
  3. 前拷贝后手动处理引用类型

示例(序列化深拷贝):

java 复制代码
class Role2 implements Serializable {
    private String roleName;
    // 构造、get/set 省略
}


class User2 implements Serializable {
    private String name;
    private Role2 role; // 引用类型

    // 深拷贝方法:序列化
    public User2 deepClone() {
        try {
            // 写入流
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            // 读取流
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            return (User2) ois.readObject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

测试:

java 复制代码
public class Test {

    public static void main(String[] args) {
        User2 prototype = new User2("张三", new Role2("超级管理员"));
        User2 cloneUser = prototype.deepClone();

        // 修改拷贝对象的引用类型
        cloneUser.getRole().setRoleName("普通用户");

        // 原对象不受影响,深拷贝成功
        System.out.println(prototype.getRole().getRoleName());
        System.out.println(cloneUser.getRole().getRoleName());
    }
}

三、浅拷贝 vs 深拷贝

特性 浅拷贝 深拷贝
实现难度 简单 稍复杂
引用类型 共用同一个对象 完全复制,独立对象
性能 较低
使用场景 无引用类型 / 允许共享 引用类型需要独立(绝大多数业务场景)

四、优缺点

优点

  1. 性能极高 :比 new 快,尤其复杂对象
  2. 简化创建:不用关心对象初始化细节
  3. 运行时创建:可动态拷贝不同状态的对象
  4. 不用依赖具体类,松耦合

缺点

  1. 每一个类都要考虑是否支持拷贝
  2. 深拷贝代码复杂
  3. 循环引用的对象拷贝会出问题

五、经典使用场景(Java 中)

1、ArrayList / HashMap 等集合的拷贝方法

2、Spring 中的 scope="prototype" bean

prototype 多例 Bean 本身是新建的,但它里面 @Autowired 注入的对象,默认全是单例,永远共用同一个引用

3、大量重复对象创建(如:报表、配置对象)

4、多线程环境下避免共享对象


总结

  1. 原型模式 = 拷贝对象创建新实例
  2. Java 实现:Cloneable + clone()
  3. 浅拷贝:快,但引用类型共享
  4. 深拷贝:完全独立,推荐序列化实现
  5. 适合:创建成本高、需要大量相似对象的场景
相关推荐
PeterLi几秒前
踩坑实录:JRebel 启动报 Mapper 重复 ID 异常,IDEA 普通启动却正常?
java·后端
Project_Observer3 分钟前
使用Zoho Projects记录工时时间后自动更新项目预算。
开发语言·数据库·人工智能·深度学习·机器学习
hixiong1234 分钟前
C#文件目录结构生成工具
开发语言·c#
小碗羊肉15 分钟前
【JavaWeb | 第五篇】JDBC
java·开发语言·数据库
江南十四行30 分钟前
Python上下文管理器与with语句——资源管理的艺术
java·jvm·数据库
书源丶31 分钟前
四十五、函数式接口与 Lambda 表达式
java·开发语言
直奔標竿32 分钟前
MySQL与Redis数据一致性实战方案(避坑指南)
java·数据库·spring boot·redis·mysql·spring·缓存
java1234_小锋34 分钟前
Java进程突然挂了如何排查?
java·开发语言
夕除38 分钟前
spring boot--04
java·spring boot
java小白小1 小时前
Guava Cache 本地缓存
java