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

一、什么是原型模式?

原型模式创建型设计模式 ,核心思想:通过复制(拷贝)一个已存在的实例(原型),来创建新的对象 ,而不是通过 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. 适合:创建成本高、需要大量相似对象的场景
相关推荐
biubiubiu07062 小时前
Maven 父子工程 SpringBoot 多模块
java·spring boot·maven
elseif1232 小时前
初学者必背【考点清单(大全)】【上篇】
开发语言·c++·笔记·学习·循环结构·分支结构·考纲
并不喜欢吃鱼2 小时前
从零开始C++----二.(下篇)模版进阶与编译全过程的复习
开发语言·c++
23471021272 小时前
4.17 学习笔记
开发语言·软件测试·笔记·python·学习
不知名的老吴2 小时前
View的三大特性之一:迟绑定
开发语言·c++·算法
tumeng07112 小时前
Spring详解
java·后端·spring
深邃-2 小时前
【Web安全】-基础环境安装:虚拟机安装,JDK环境安装(1)
java·开发语言·计算机网络·安全·web安全·网络安全·安全架构
小雅痞2 小时前
[Java][Leetcode hard] 135. 分发糖果
java·算法·leetcode
前端老石人2 小时前
前端网站换肤功能的 3 种实现方案
开发语言·前端·css·html