当先锋百科网

首页 1 2 3 4 5 6 7

1 原型模式的简单介绍

原型模式(Prototype Pattern)是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

原型模式主要适用于以下场景:
1、类初始化消耗资源较多;
2、new 产生的一个对象需要非常繁琐的过程(数据准备、访问权限等);
3、构造函数比较复杂;
4、循环体中生产大量对象时。

典型的原型应用有BeanUtils.copy(),JSON.parseObject,JDK中的Cloneable等。

原型模式在拷贝对象时又分为浅克隆与深克隆,下面就举个简单的例子来分析一下。
假设现在的业务是要批量插入一个班的学生数据,在学生数据中,班主任、班级、年级、学校等是一样的,那么在循环的封装学生数据的时候可以使用克隆来减少一些工作量。

2 浅克隆

一个标准的原型模式代码,应该是需要一个定义了克隆方法的接口的(类似下方代码这样的)。

public interface Prototype {
    Prototype clone();
}

在java里面,所有的class类都继承Object类,在Object类有个clone()方法,它被定义成只能在子类中调用,所以如果我们想要在外部的类中调用clone()方法需要将其重写,但是光重写是不够的,在调用的时候会报CloneNotSupportedException异常,我们查看Object的源码注释就可以知道

    /**
     * .....
     * The class {@code Object} does not itself implement the interface
     * {@code Cloneable}, so calling the {@code clone} method on an object
     * whose class is {@code Object} will result in throwing an
     * exception at run time.
     *
     * @return     a clone of this instance.
     * @throws  CloneNotSupportedException  if the object's class does not
     *               support the {@code Cloneable} interface. Subclasses
     *               that override the {@code clone} method can also
     *               throw this exception to indicate that an instance cannot
     *               be cloned.
     * @see java.lang.Cloneable
     */
    protected native Object clone() throws CloneNotSupportedException;

所以最终我们创建的具体需要克隆的对象StudentPrototype是下面这样子的(这里为了演示方便,用了lombok插件):

@Data
public class StudentPrototype implements Cloneable {
    /** 学生id */
    private Integer studentId;
    /** 学生名称 */
    private String studentName;
    /** 生日 */
    private Date birthday;
    /** 班级id */
    private Integer classId;
    /** 年级id */
    private Integer gradeId;
    /** 学校id */
    private Integer schoolId;
    /** 家长信息 */
    private ParentInfo parentInfo;

    @Override
    protected StudentPrototype clone() throws CloneNotSupportedException {
        return (StudentPrototype)super.clone();
    }
}

再创建一个家长类ParentInfo:

@Getter
@Setter
public class ParentInfo {
    private String name;
    private String phone;
}

测试代码:

public class PrototypeTest {
    public static void main(String[] args) {
        StudentPrototype sp = new StudentPrototype();
        sp.setStudentId(111);
        sp.setStudentName("钢铁侠");
        sp.setBirthday(new Date());
        sp.setClassId(222);
        sp.setGradeId(333);
        sp.setSchoolId(444);
        ParentInfo parentInfo = new ParentInfo();
        parentInfo.setName("绿巨人");
        parentInfo.setPhone("13012345678");
        sp.setParentInfo(parentInfo);
        try {
            StudentPrototype spClone = sp.clone();
            System.out.println("原对象====>"+sp);
            System.out.println("克隆对象====>"+spClone);
            System.out.println("原对象引用对象地址值====>"+sp.getParentInfo());
            System.out.println("克隆对象引用对象地址值====>"+spClone.getParentInfo());
            System.out.println("对象地址比较:" + (sp.getParentInfo() == spClone.getParentInfo()));
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

输出结果:

从测试结果看出parentInfo 的引用地址是相同的,意味着复制的不是值,而是引用的地址。这样的话, 如果我们修改任意一个对象中的属性值, sp和 spCone 的parentInfo 值都会改变,意味着在修改了一个学生的家长名字叫“张三”,那所有的学生家长名字就都叫张三。这就是我们常说的浅克隆。只是完整复制了值类型数据,没有赋值引用对象。换言之,所有的引用对象仍然指向原来的对象,显然不是我们想要的结果。下面我们来看深度克隆继续改造。

2 深度克隆

浅克隆中的对象引用是一样的,其内的数据属于公共变量,那根据我们的需求,我们希望克隆出来的对象都是独立的个体,那我们就需要使每个克隆出来的对象内部引用的对象都是独立的,对于上面的代码,我们有几种方式修改。

2.1 重写clone()

    @Override
    protected StudentPrototype clone() throws CloneNotSupportedException {
        StudentPrototype clone = (StudentPrototype) super.clone();
        clone.setParentInfo(new ParentInfo());
        return clone;
    }

这里就是手动的将内部引用的对象进行处理,我们常用的ArrayList 就实现了Cloneable 接口,来看代码clone()方法的实现:

    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

2.2 序列化与反序列化

在上篇叫单例模式的文章中也有介绍到,对象是可以通过反序列化创建的,并且创建出来的对象及其内部的引用和原来的不一样,会重新开辟内存。
要先实现序列化接口,自身及其内的类都要实现序列化接口


加一个深度克隆方法:

    public StudentPrototype 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 (StudentPrototype)ois.readObject();
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

测试:

    public static void main(String[] args) {
        StudentPrototype sp = new StudentPrototype();
        sp.setStudentId(111);
        sp.setStudentName("钢铁侠");
        sp.setBirthday(new Date());
        sp.setClassId(222);
        sp.setGradeId(333);
        sp.setSchoolId(444);
        ParentInfo parentInfo = new ParentInfo();
        parentInfo.setName("绿巨人");
        parentInfo.setPhone("13012345678");
        sp.setParentInfo(parentInfo);
        
        StudentPrototype spClone = sp.deepClone();
        System.out.println("原对象====>" + sp);
        System.out.println("克隆对象====>" + spClone);
        System.out.println("原对象引用对象地址值====>" + sp.getParentInfo());
        System.out.println("克隆对象引用对象地址值====>" + spClone.getParentInfo());
        System.out.println("对象地址比较:" + (sp.getParentInfo() == spClone.getParentInfo()));
    }

输出结果: