设计模式 - 原型模式

125 阅读4分钟

原型模式的核心概念“克隆”

克隆分为“浅克隆 shallow clone”、“深克隆 deep clone”

浅克隆

代码文件如下

image.png

/**
 * 原型模式 - 浅克隆
 * 实现两件事:
 * 1. 继承接口
 * 2. 重写方法
 * 
 * 原理:只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存
 */
public class Video implements Cloneable {
    private String name;
    private Date dataTime;

    private Video() {}

    public Video(String name, Date dataTime) {
        this.name = name;
        this.dataTime = dataTime;
    }

    public String getName() {
        return name;
    }

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

    public Date getDataTime() {
        return dataTime;
    }

    public void setDataTime(Date dataTime) {
        this.dataTime = dataTime;
    }

    @Override
    public String toString() {
        return "Video{" +
                "name='" + name + ''' +
                ", dataTime=" + dataTime +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class Test {

    public static void main(String[] args) throws CloneNotSupportedException {
        Date date1 = new Date();
        Video v1 = new Video("v1", date1);
        System.err.println("v1 => " + v1);
        System.err.println("v1:hashCode => " + v1.hashCode());

        Video v2 = (Video) v1.clone();
        v2.setName("v2");
        System.err.println("v2 => " + v2);
        System.err.println("v2:hashCode => " + v2.hashCode());
    }
}

执行结果

v1 => Video{name='v1', dataTime=Mon May 22 17:05:54 CST 2023}
v1:hashCode => 821270929
v2 => Video{name='v2', dataTime=Mon May 22 17:05:54 CST 2023}
v2:hashCode => 1160460865

执行的内存指向如下

image.png

因此当重新设定date1的值,会发生克隆对象被一起改变,修改代码如下

public class Test {

    public static void main(String[] args) throws CloneNotSupportedException {
//        Date date1 = new Date();
//        Video v1 = new Video("v1", date1);
//        System.err.println("v1 => " + v1);
//        System.err.println("v1:hashCode => " + v1.hashCode());
//
//        Video v2 = (Video) v1.clone();
//        v2.setName("v2");
//        System.err.println("v2 => " + v2);
//        System.err.println("v2:hashCode => " + v2.hashCode());

        Date date1 = new Date();
        Video v1 = new Video("v1", date1);
        Video v2 = (Video) v1.clone();
        v2.setName("v2");
        System.err.println("v1 => " + v1);
        System.err.println("v1:hashCode => " + v1.hashCode());
        System.err.println("v2 => " + v2);
        System.err.println("v2:hashCode => " + v2.hashCode());

        System.err.println("=============");
        date1.setTime(22131231);

        // 此时的v1、v2的date应该不一致,但实际上被一起改变了
        System.err.println("v1 => " + v1);
        System.err.println("v1:hashCode => " + v1.hashCode());
        System.err.println("v2 => " + v2);
        System.err.println("v2:hashCode => " + v2.hashCode());
    }
}

执行结果如下

v1 => Video{name='v1', dataTime=Mon May 22 17:18:11 CST 2023}
v1:hashCode => 821270929
v2 => Video{name='v2', dataTime=Mon May 22 17:18:11 CST 2023}
v2:hashCode => 1160460865
=============
v1 => Video{name='v1', dataTime=Thu Jan 01 14:08:51 CST 1970}
v1:hashCode => 821270929
v2 => Video{name='v2', dataTime=Thu Jan 01 14:08:51 CST 1970}
v2:hashCode => 1160460865

因此,浅克隆是不安全的,由此引申出"深克隆"的概念

深克隆

期望的内存指向

image.png

重点在:重写clone()

/**
 * 原型模式 - 深克隆
 * 实现两件事:
 * 1. 继承一个接口(Cloneable) 或 继承2个接口(Cloneable、Serializable)
 * 2. 重写方法
 *
 * 原理:复制并创建一个一摸一样的对象,不共享内存,修改新对象,旧对象保持不变
 */
public class Video implements Cloneable {
    private String name;
    private Date dataTime;

    private Video() {}

    public Video(String name, Date dataTime) {
        this.name = name;
        this.dataTime = dataTime;
    }

    public String getName() {
        return name;
    }

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

    public Date getDataTime() {
        return dataTime;
    }

    public void setDataTime(Date dataTime) {
        this.dataTime = dataTime;
    }

    @Override
    public String toString() {
        return "Video{" +
                "name='" + name + ''' +
                ", dataTime=" + dataTime +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Object obj = super.clone();
        Video v = (Video) obj;

        // 将这个对象的属性也进行克隆,实现深克隆
        v.dataTime = (Date) this.dataTime.clone();

        // 还有一种方案是 继承Serializable,进行序列化/反序列化,但这会与IO进行打交道,效率上会有问题,所以不推荐

        return obj;
    }
}

执行结果如下

v1 => Video{name='v1', dataTime=Mon May 22 17:30:28 CST 2023}
v1:hashCode => 821270929
v2 => Video{name='v2', dataTime=Mon May 22 17:30:28 CST 2023}
v2:hashCode => 1160460865
=============
v1 => Video{name='v1', dataTime=Thu Jan 01 14:08:51 CST 1970}
v1:hashCode => 821270929
v2 => Video{name='v2', dataTime=Mon May 22 17:30:28 CST 2023}
v2:hashCode => 1160460865

深克隆的第二种方案 序列化/反序列化(与I/O打交道,效率有影响,不推荐)

重点在:多继承一个Serializable、重写clone()

/**
 * 原型模式 - 深克隆
 * 实现两件事:
 * 1. 继承一个接口(Cloneable) 或 继承2个接口(Cloneable、Serializable)
 * 2. 重写方法
 *
 * 原理:复制并创建一个一摸一样的对象,不共享内存,修改新对象,旧对象保持不变
 */
public class Video implements Cloneable, Serializable {
    private String name;
    private Date dataTime;

    private Video() {}

    public Video(String name, Date dataTime) {
        this.name = name;
        this.dataTime = dataTime;
    }

    public String getName() {
        return name;
    }

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

    public Date getDataTime() {
        return dataTime;
    }

    public void setDataTime(Date dataTime) {
        this.dataTime = dataTime;
    }

    @Override
    public String toString() {
        return "Video{" +
                "name='" + name + ''' +
                ", dataTime=" + dataTime +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        try {
            // 序列化
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            // 写入到当前类(也可以写入到文件)
            oos.writeObject(this);

            // 反序列化
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            return ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

执行结果如下

v1 => Video{name='v1', dataTime=Mon May 22 17:37:40 CST 2023}
v1:hashCode => 258952499
v2 => Video{name='v2', dataTime=Mon May 22 17:37:40 CST 2023}
v2:hashCode => 1637070917
=============
v1 => Video{name='v1', dataTime=Thu Jan 01 14:08:51 CST 1970}
v1:hashCode => 258952499
v2 => Video{name='v2', dataTime=Mon May 22 17:37:40 CST 2023}
v2:hashCode => 1637070917

提一嘴:

  1. Spring Bean使用单例模式、原型模式
  2. 一般原型模式+工厂模式配套使用
  3. new一些场景下可以使用clone代替