Skip to content
On this page

原型模式

概述

用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象

结构

原型模式包含如下角色:

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

image-20220918202619961

实现

原型模式的克隆分为浅克隆和深克隆

  • 浅克隆:创建一个新对象,新对象的属性和原来对象的完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址
  • 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不在指向原有对象地址

Java中的Object类中提供了clone()方法来实现浅克隆。Cloneable接口是上面类图中的抽象原型类(Prototype),而实现了Cloneable接口的子实现类就是具体的原型类(Realizetype),代码如下:

具体原型类(Realizetype)

java
package com.huangjiliang.design.heima.create.prototype.demo1;

public class Realizetype implements Cloneable{

    public Realizetype() {
        System.out.println("具体原型创建成功!");
    }

    @Override
    protected Realizetype clone() throws CloneNotSupportedException {
        System.out.println("具体原型复制成功");
        return (Realizetype)super.clone();
    }
}

测试类(Client)

java
package com.huangjiliang.design.heima.create.prototype.demo1;

/**
 * 客户端-演示原型模式-创建
 */
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {
        Realizetype realizetype = new Realizetype();
        Realizetype clone = realizetype.clone();
        // 创建的对象是否和克隆出来的对象一致:false
        System.out.println("创建的对象是否和克隆出来的对象一致:" + (realizetype == clone));
    }
}

案例

用原型模式生产“三好学生”奖状

同一个学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,可以使用原型模式克隆出来多个“三好学生”奖状出来,然后再修改奖状上的学生名字即可。其类型图如下:

获奖类

java
package com.huangjiliang.design.heima.create.prototype.demo2;

/**
 * 三好学生奖状
 */
public class Citation implements Cloneable{

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Citation() {
        System.out.println("具体原型创建成功!");
    }

    @Override
    protected Citation clone() throws CloneNotSupportedException {
        System.out.println("具体原型复制成功");
        return (Citation)super.clone();
    }

    public void show() {
        System.out.println(name + "获得了三好学生奖状!");
    }
}

测试类

java
package com.huangjiliang.design.heima.create.prototype.demo2;

/**
 * 客户端-演示原型模式-创建
 */
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {
        Citation r1 = new Citation();
        r1.setName("黄继良");
        r1.show();
        Citation r2 = r1.clone();
        r2.setName("刘德华");
        r2.show();
    }
}

使用场景

  • 对象的创建非常复杂,可以使用原型模式快捷的创建对象
  • 性能和安全要求比较高

浅克隆

将上面的“三好学生”奖状的案例中的Citation类的name属性修改为Student类型的属性。代码如下

学生类

java
package com.huangjiliang.design.heima.create.prototype.demo3;

import lombok.Data;
import lombok.experimental.Accessors;

/**
 * 学生类
 */
@Data
@Accessors(chain = true)
public class Student {

    private String id;

    private String name;

    private String studentNum;
    
}

三好学生奖状

java
package com.huangjiliang.design.heima.create.prototype.demo3;

import lombok.Data;

/**
 * 三好学生奖状
 */
@Data
public class Citation implements Cloneable{

    private Student student;

    public Citation() {
        System.out.println("具体原型创建成功!");
    }

    @Override
    protected Citation clone() throws CloneNotSupportedException {
        System.out.println("具体原型复制成功");
        return (Citation)super.clone();
    }

    public void show() {
        System.out.println(student.getName() + "获得了三好学生奖状!");
    }
}

测试类

java
package com.huangjiliang.design.heima.create.prototype.demo3;

/**
 * 客户端-深克隆
 */
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {
        Citation r1 = new Citation();
        Student student1 = new Student();
        student1.setName("黄继良").setId("001").setStudentNum("1000");
        r1.setStudent(student1);

        Citation r2 = r1.clone();
        Student student2 = r2.getStudent();
        student2.setName("刘德华");
        r1.show();// 刘德华获得了三好学生奖状!  【r1引用的student原本是黄继良,而被student2修改了】
        r2.show();// 刘德华获得了三好学生奖状!
        // student1和student2是否是同一个对象:true【证明引用的是同一个对象】
        System.out.println("student1和student2是否是同一个对象:" + (student1 == student2));
    }
}
  • student1和student2是同一个对象,所以student2修改name属性之后,两个Citation对象中显示都是student2修改后的name,这就是浅克隆的效果。对具体原型类(Citation)中国红的引用类型的属性进行引用的复制。这种情况需要使用深克隆,而进行深克隆需要使用对象流。

深克隆

1、创建序列化工具类

java
package com.huangjiliang.design.heima.create.prototype.demo4;

import java.io.*;

/**
 * 类名 SerialCloneUtils
 * 描述 序列化克隆工具
 */
public class SerialCloneUtils{
    /**
     * 使用ObjectStream序列化实现深克隆
     * @return Object obj
     */
    public static <T extends Serializable> T deepClone(T t) throws CloneNotSupportedException {
        // 保存对象为字节数组
        try {
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            try(ObjectOutputStream out = new ObjectOutputStream(bout)) {
                out.writeObject(t);
            }

            // 从字节数组中读取克隆对象
            try(InputStream bin = new ByteArrayInputStream(bout.toByteArray())) {
                ObjectInputStream in = new ObjectInputStream(bin);
                return (T)(in.readObject());
            }
        } catch (IOException | ClassNotFoundException e) {
            CloneNotSupportedException cloneNotSupportedException = new CloneNotSupportedException();
            e.initCause(cloneNotSupportedException);
            throw cloneNotSupportedException;
        }
    }
}

2、创建序列化抽象类

java
package com.huangjiliang.design.heima.create.prototype.demo4;

import java.io.Serializable;

/**
 * 类名 SerialClone
 * 描述 序列化克隆类,只有继承该类,就可以实现深克隆
 */
public class SerialClone implements Cloneable, Serializable {
    private static final long serialVersionUID = 5794148504376232369L;

    @Override
    public Object clone() throws CloneNotSupportedException {
        return SerialCloneUtils.deepClone(this);
    }
}

3、三好学生类修改-继承自定义克隆类

java
package com.huangjiliang.design.heima.create.prototype.demo4;

import lombok.Data;

/**
 * 三好学生奖状
 */
public class Citation extends SerialClone {

    private Student student;

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }

    public Citation() {
        System.out.println("具体原型创建成功!");
    }

    public void show() {
        System.out.println(student.getName() + "获得了三好学生奖状!");
    }
}

4、学生类修改-继承自定义克隆类

java
package com.huangjiliang.design.heima.create.prototype.demo4;

import lombok.Data;
import lombok.experimental.Accessors;

/**
 * 学生类
 */
public class Student extends SerialClone {

    private static final long serialVersionUID = 1L;

    private String id;

    private String name;

    private String studentNum;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getStudentNum() {
        return studentNum;
    }

    public void setStudentNum(String studentNum) {
        this.studentNum = studentNum;
    }
}

5、测试类

java
package com.huangjiliang.design.heima.create.prototype.demo4;

/**
 * 客户端-深克隆
 */
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {
        Citation r1 = new Citation();
        Student student1 = new Student();
        student1.setName("黄继良");
        r1.setStudent(student1);

        Citation r2 = (Citation)r1.clone();
        Student student2 = r2.getStudent();
        student2.setName("刘德华");
        r1.show();// 黄继良获得了三好学生奖状!
        r2.show();// 刘德华获得了三好学生奖状!
        // student1和student2是否是同一个对象:false
        System.out.println("student1和student2是否是同一个对象:" + (student1 == student2));
    }
}
  • 测试后发现student1和student2并不是指向同一个引用地址
  • 使用此方式使用Lombok的@Data注解会报错。原因未知
  • 还有其他深克隆方式-如:使用Json工具类进行序列化等