《深入剖析原型模式:浅克隆、深克隆与单例模式的碰撞》

3.原型模式

一、引言

在 Java 编程中,原型模式(Prototype)是一种创建对象的方式,通过拷贝原型实例来创建新的对象,为对象的创建提供了一种高效且灵活的途径。本文将详细探讨原型模式的概念、包含的角色、浅克隆与深克隆的实现,以及克隆对单例模式的影响和相应的解决办法。

二、原型模式的定义

原型模式是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。调用者不需要知道任何创建细节,不调用构造函数。

三、原型模式的角色

  1. 抽象原型类 :规定了具体原型对象必须实现的 clone() 方法。
  2. 具体原型类 :实现抽象原型类的 clone() 方法,是可被复制的对象。
  3. 访问类 :使用具体原型类中的 clone() 方法来复制新的对象。

四、简单的原型模式示例

java 复制代码
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Cloneable {
    private String name;
    private String sex;
    private Integer age;

    /**
     * 重写 clone 方法,实现对象的复制
     * @return 复制后的对象
     * @throws CloneNotSupportedException 若不支持克隆则抛出此异常
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    /**
     * 主函数,用于测试原型模式的基本用法
     * @param args 命令行参数
     * @throws Exception 可能抛出的异常
     */
    public static void main(String[] args) throws Exception{
        Student stu1 = new Student("小明", "男", 18);
        // 通过克隆创建 stu2 对象
        Student stu2 = (Student)stu1.clone();
        stu2.setName("小红");
        // 输出 stu1 的信息
        System.out.println(stu1); // Student(name=小明, sex=男, age=18)
        // 输出 stu2 的信息
        System.out.println(stu2); // Student(name=小红, sex=男, age=18)
    }
}

可以看到,把一个学生复制过来,只是改了姓名而已,其他属性完全一样没有改变。需要注意的是,一定要在被拷贝的对象上实现 Cloneable 接口,否则会抛出 CloneNotSupportedException 异常。

四、浅克隆

浅克隆的定义:浅克隆创建一个新对象,新对象的属性和原来对象完全相同,但对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。

java 复制代码
@Data
public class Clazz implements Cloneable {
    private String name;
    private Student student;

    /**
     * 重写 clone 方法,实现浅克隆
     * @return 浅克隆后的对象
     * @throws CloneNotSupportedException 若不支持克隆则抛出此异常
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Serializable {
    private String name;
    private String sex;
    private Integer age;
}

/**
 * 主函数,用于测试浅克隆
 * @param args 命令行参数
 * @throws Exception 可能抛出的异常
 */
public static void main(String[] args) throws Exception{
    Clazz clazz1 = new Clazz();
    clazz1.setName("大一");
    Student stu1 = new Student("小明", "男", 18);
    clazz1.setStudent(stu1);
    // 输出 clazz1 的信息
    System.out.println(clazz1); // Clazz(name=大一, student=Student(name=小明, sex=男, age=18))
    Clazz clazz2 = (Clazz)clazz1.clone();
    Student stu2 = clazz2.getStudent();
    stu2.setName("小红");
    // 输出 clazz1 的信息
    System.out.println(clazz1); // Clazz(name=大一, student=Student(name=小红, sex=男, age=18))
    // 输出 clazz2 的信息
    System.out.println(clazz2); // Clazz(name=大一, student=Student(name=小红, sex=男, age=18))
}

可以看到,当修改了 stu2 的姓名时,stu1 的姓名同样也被修改了,这说明 stu1stu2 是同一个对象,这就是浅克隆的特点,对具体原型类中的引用类型的属性进行引用的复制。同时,这也可能是浅克隆所带来的弊端,因为结合该例子的原意,显然是想在班级中新增一名叫小红的学生,而非让所有的学生都改名叫小红,于是我们这里就要使用深克隆。

五、深克隆

深克隆的定义:深克隆创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

java 复制代码
@Data
public class Clazz implements Cloneable, Serializable {
    private String name;
    private Student student;

    /**
     * 重写 clone 方法,实现浅克隆
     * @return 浅克隆后的对象
     * @throws CloneNotSupportedException 若不支持克隆则抛出此异常
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    /**
     * 实现深克隆的方法
     * @return 深克隆后的对象
     * @throws IOException 输入输出异常
     * @throws ClassNotFoundException 类未找到异常
     */
    protected Object deepClone() 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 ois.readObject();
    }
}

/**
 * 主函数,用于测试深克隆
 * @param args 命令行参数
 * @throws Exception 可能抛出的异常
 */
public static void main(String[] args) throws Exception{
    Clazz clazz1 = new Clazz();
    clazz1.setName("大一");
    Student stu1 = new Student("小明", "男", 18);
    clazz1.setStudent(stu1);
    Clazz clazz3 = (Clazz)clazz1.deepClone();
    Student stu3 = clazz3.getStudent();
    stu3.setName("王五");
    // 输出 clazz1 的信息
    System.out.println(clazz1); // Clazz(name=大一, student=Student(name=小明, sex=男, age=18))
    // 输出 clazz3 的信息
    System.out.println(clazz3); // Clazz(name=大一, student=Student(name=王五, sex=男, age=18))
}

可以看到,当修改了 stu3 的姓名时,stu1 的姓名并没有被修改了,这说明 stu3stu1 已经是不同的对象了,说明 Clazz 中的 Student 也被克隆了,不再指向原有对象地址,这就是深克隆。这里需要注意的是,Clazz 类和 Student 类都需要实现 Serializable 接口,否则会抛出 NotSerializableException 异常。

六、克隆破坏单例与解决办法

java 复制代码
// Clazz 类
@Data
public class Clazz implements Cloneable, Serializable {
    private static Clazz clazz = new Clazz();
    private String name;
    private Student student;

    /**
     * 私有构造函数,防止外部创建实例
     */
    private Clazz(){}

    /**
     * 获取单例实例的方法
     * @return 单例实例
     */
    public static Clazz getInstance() {
        return clazz;
    }

    /**
     * 重写 clone 方法,解决克隆破坏单例的问题
     * @return 单例实例
     * @throws CloneNotSupportedException 若不支持克隆则抛出此异常
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return clazz;
    }
}

/**
 * 测试克隆是否破坏单例
 * @param args 命令行参数
 * @throws Exception 可能抛出的异常
 */
public static void main(String[] args) throws Exception{
    Clazz clazz1 = Clazz.getInstance();
    Clazz clazz2 = (Clazz)clazz1.clone();
    // 输出比较结果
    System.out.println(clazz1 == clazz2); // false

    // 重写 clone 方法后的测试
    Clazz clazz3 = Clazz.getInstance();
    Clazz clazz4 = (Clazz)clazz3.clone();
    System.out.println(clazz3 == clazz4); // true
}

可以看到 clazz1clazz2 并不相等,也就是说他们并不是同一个对象,单例被破坏了。

解决办法:

  1. 不实现 Cloneable 接口即可,但不实现 Cloneable 接口进行 clone 则会抛出 CloneNotSupportedException 异常。
  2. 重写 clone() 方法,返回单例对象。

另外我们知道,单例就是只有一个实例对象,如果重写了 clone() 方法保证单例的话,那么通过克隆出来的对象则不可以重新修改里面的属性,因为修改以后就会连同克隆对象一起被修改,所以是需要单例还是克隆,在实际应用中需要好好衡量。

七、总结

  1. 适用场景:
    • 类初始化消耗资源较多。
    • new 产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)。
    • 构造函数比较复杂。
    • 循环体中生产大量对象时。
  2. 优点:
    • 性能优良,Java 自带的原型模式是基于内存二进制流的拷贝,比直接 new 一个对象性能上提升了许多。
    • 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,简化了创建的过程。
  3. 缺点:
    • 必须配备克隆(或者可拷贝)方法。
    • 当对已有类进行改造的时候,需要修改代码,违反了开闭原则。
    • 深克隆、浅克隆需要运用得当。
相关推荐
骄马之死2 小时前
SpringMVC + SpringBoot 核心知识点总结
java·spring boot·后端
zhengfei6112 小时前
第3章 Agent 类型分类与设计模式
设计模式
刀法如飞3 小时前
一文搞懂DDD 领域驱动设计思想原理
设计模式·架构·代码规范
郑洁文3 小时前
基于Spring Boot的流浪动物救助网站
java·spring boot·后端·毕设·流浪动物救助
螺丝钉code4 小时前
JAVA项目 Claude code CLAUDE.md 到底应该怎么写
java·人工智能·claude code
摇滚侠5 小时前
Maven 入门+高深 单一架构案例 54-59
java·架构·maven·intellij-idea
VidDown6 小时前
Webhook 调试器:让第三方回调“原形毕露”
java·开发语言·javascript·编辑器·postman
折哥的程序人生 · 物流技术专研6 小时前
Java 23 种设计模式:从踩坑到精通 | 原型模式 —— 克隆对象,深拷贝与浅拷贝的坑你踩过吗?
java·设计模式·架构·原型模式·单一职责原则
装不满的克莱因瓶6 小时前
基于 OpenResty 扩展开发实现动态服务注册与发现能力
java·开发语言·架构·openresty