设计模式之原型模式

「这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战

本篇文章是设计模式专题的第五篇文章,我会将遇到的设计模式都一一总结在该专题下,我会把自己对每一种设计模式的感悟写下来,以及在实际工作中我们该如何去灵活应用这些设计模式,欢迎大家关注。本篇文章我们就来讲一讲,用于创建重复对象的原型模式。

原型模式的简单介绍

原型模式也属于创建型模式的一种,它是用来创建重复对象的,也就是我们常说的拷贝对象。

原型模式是通过实现一个原型接口,然后调用clone()就可以完成对象复制,需要注意的是clone()方法是Object提供的,当需要深拷贝的时候,就需要通过重写克隆方法进行对象的拷贝。

原型模式类图:

image.png

原型模式扩展:

原型模式可以进行扩展,在原有的基础上增加一个原型管理器 PrototypeManager 类。该类用 HashMap 缓存多个复制的原型,客户端可以通过管理器的 get(String id) 方法从中获取复制的原型。

image.png

浅拷贝与深拷贝:

  • 浅拷贝:不重写clone方法的都是浅拷贝,浅拷贝如果对象内属性是引用类型的话,拷贝的是引用对象的地址。
  • 深拷贝:深拷贝需要重写clone方法,通过串行话或者其他方式将属性是引用类型的创建空间拷贝一份独立的。

原型模式的具体实现思路

  • 浅拷贝:直接实现Cloneable接口即可

  • 深拷贝:需要实现Cloneable接口,并且需要重写clone方法。

  • 原型管理器:

    • 创建抽象原型对象,继承Cloneable接口
    • 创建抽象原型对象的具体实现
    • 创建原型管理器,初始化缓存,提供获取拷贝对象的方法

原型模式的具体实现方案

  • 浅拷贝

    public class Prototype implements Cloneable {
        @Override
        public Object clone()  throws CloneNotSupportedException {
            return super.clone();
        }
    }
    复制代码
  • 深拷贝

    深拷贝的方式有很多,我们的目的是为了将引用类型重新开辟空间,而不是引用原引用的地址。

    // 实现的方式有很多,针对属性进行再次拷贝赋值,或者使用序列化
    // 1. 再次拷贝
    public class Prototype implements Cloneable {
        private OtherPrototype other;
        
        @Override
        public Object clone()  throws CloneNotSupportedException {
            Object obj = super.clone();
            // 将原引用的内容,重新开辟空间保存一份,使用新的引用
            obj.setOther((OtherPrototype) obj.getOther.clone());
            return obj;
        }
    }
    // 2. 序列化实现 注意需要实现Serializable
    public class Prototype implements Serializable {
        public Object deepClone() throws Exception
        {
            // 序列化
            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();
        }
    }
    复制代码
  • 原型管理器方式

    // 抽象原型对象
    public interface Prototype extends Cloneable {
        @Override
        public Object clone();
    }
    // 真实原型对象
    public class Realizetype implements Prototype {
        @Override
        public Object clone()  throws CloneNotSupportedException {
            return super.clone();
        }
    }
    // 原型管理器
    class ProtoTypeManager {
        // 缓存
        private HashMap<String, Object> cache = new HashMap<String, Object>();
        
        // 初始化缓存
        public ProtoTypeManager() {
            cache.put("realizetype", new Realizetype());
        }
        
        // 向缓存中添加原型对象
        public void addProtoType(String key, Object obj) {
            cache.put(key, obj);
        }
        // 获取对应原型对象的拷贝对象
        public Prototype getPrototype(String key) {
            Object temp = cache.get(key);
            return (Prototype) temp.clone();
        }
    }
    复制代码

原型模式的优缺点

优点

  • 原型模式性能很好,基于二进制的流复制比new性能更好。
  • 原型模式可以避免构造函数的约束。
  • Java 为我们将原型模式封装好,我们使用很方便。

缺点

  • 当需要深拷贝时,一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候,拷贝会变的很困难。
  • 必须实现 Cloneable 接口。
  • clone方法位于类的内部,如果需要对已有类进行改造,需要改变类的内部结构,违背了开闭原则。

原型模式的适用场景

  1. 对象之间相同或相似,只是个别的几个属性不同的时候。
  2. 创建对象成本较大,比如初始化时间长,占用CPU资源多,占用网络资源多等,需要优化资源。
  3. 创建一个对象需要繁琐的数据准备或访问权限等,需要提高性能或者提高安全性。
  4. 系统中大量使用该类对象,需要各个调用者给它的属性重新赋值。
  5. 当一个对象需要提供给其他对象访问,并且各个调用者都需要修改其值时。
  6. 原型模式很少单独出现,一般是伴随工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。

原型模式总结

原型模式虽然实现起来比较简单,用法也很简单。但是深拷贝和浅拷贝的问题我们一定要搞清楚,有时候使用浅拷贝导致数据混乱,排查起来也是很困难。再就是我们要善用原型管理器,通过原型管理器创建克隆对象会使我们事半功倍。