带你了解不一样的原型模式

459 阅读5分钟

这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战

带你了解不一样的原型模式

概念

原型模式(Prototype Pattern)是指原型实例制定创建对象的种类,并且通过拷贝这些原型创建新的对象,本体给外部提供一个克隆体进行使用,在类的过程中属于创建型模式。

原型模式的应用场景

  • 当类的初始化加载需要消耗过多的资源的时候,可以进行资源优化

  • 对于性能和安全又特殊要求的时候,比如访问权限等new产生一个对象中比较繁琐的过程

  • 一个对象对应着多个修改的场景

  • 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时可以考虑使用原型模式拷贝多个对象供调用者使用。

浅拷贝方法

原型模式的核心就在于拷贝对象。以系统中已经存在的一个对象为原型,直接给予该对象的二进制内存进行拷贝,不需要经历对象的初始化过程,不需要调用构造函数,可以直接的拷贝过后直接使用,进而优化资源,可以提升很多性能。而拷贝一般分为浅拷贝和深拷贝。

浅拷贝是创建一个新的对象,并且这个对象是通过位拷贝进行获取的,这个对象有着原始对象属性值的一份精确拷贝。如果拷贝的属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象

代码实现

  • ConcretePrototype
public class ConcretePrototype implements Cloneable {


    private Integer age;
    private String name;
    private List hobbies;

    @Override
    public ConcretePrototype clone() {

        try {
            return (ConcretePrototype) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }    
}
  • test
public static void main(String[] args) {
    ConcretePrototype concretePrototype = new ConcretePrototype();
    concretePrototype.setAge(20);
    concretePrototype.setName("张三");
    List hobbies = new ArrayList();
    hobbies.add("游戏");
    hobbies.add("篮球");
    concretePrototype.setHobbies(hobbies);

    ConcretePrototype cloneType = concretePrototype.clone();
    cloneType.getHobbies().add("技术");
cloneType.setName("李四");
cloneType.setAge(30);
    System.out.println("原型对象:" + concretePrototype);
    System.out.println("克隆对象:" + cloneType);
    System.out.println(concretePrototype == cloneType);
    System.out.println("原型对象的爱好:" + concretePrototype.getHobbies());
    System.out.println("克隆对象的爱好:" + cloneType.getHobbies());
    System.out.println(concretePrototype.getHobbies() == cloneType.getHobbies());
}
  • 结果

image.png

在这个例子中,拷贝的类ConcretePrototype实现了实现了Clonable接口并重写Object类的clone()方法。然后在测试类中声明一个concretePrototype类,然后设置age、name、hobbies等属性值,然后通过clone方法获取了一个新的拷贝类cloneType,cloneType类重新设置age和name属性,从结果中可以看到,拷贝对象cloneType对age和name属性的改变并没有影响到原始对象,但是对引用对象的hobbies属性的影响到了原始对象的引用值。

  • 浅拷贝基本原理图

image.png

深拷贝方法

深拷贝是比浅拷贝更全的一种方法,是会拷贝原始对象的所有的属性并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。

代码实现

  • ConcretePrototype
public class ConcretePrototype implements Cloneable, Serializable {


    private Integer age;
    private String name;
    private List hobbies;

    @Override
    public ConcretePrototype clone() {

        try {
            return (ConcretePrototype) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

    public ConcretePrototype deepCloneHobbies() {
        try {
            ConcretePrototype result = (ConcretePrototype) super.clone();
            result.hobbies = (List) ((ArrayList) result.hobbies).clone();
            return result;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
    public ConcretePrototype 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 (ConcretePrototype) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
  • MainTest
public static void main(String[] args) {
    //创建原型对象
    ConcretePrototype prototype = new ConcretePrototype();
    prototype.setAge(18);
    prototype.setName("Tom");
    List<String> hobbies = new ArrayList<String>();
    hobbies.add("书法");
    hobbies.add("美术");
    prototype.setHobbies(hobbies);

    //拷贝原型对象
    ConcretePrototype cloneType = prototype.deepCloneHobbies();
    cloneType.getHobbies().add("技术控");
    System.out.println("原型对象:" + prototype);
    System.out.println("克隆对象:" + cloneType);
    System.out.println(prototype == cloneType);
    System.out.println("原型对象的爱好:" + prototype.getHobbies());
    System.out.println("克隆对象的爱好:" + cloneType.getHobbies());
    System.out.println(prototype.getHobbies() == cloneType.getHobbies());
}
  • 结果

image.png

在这个例子中,拷贝的类ConcretePrototype实现了实现了Clonable接口并重写Object类的clone()方法。然后自身也实现了序列化操作,并且自定义实现了deepClone深拷贝方法,然后在测试类中声明一个concretePrototype类,然后设置age、name、hobbies等属性值,然后通过clone方法获取了一个新的拷贝类cloneType,cloneType拷贝类在hobbites引用属性添加了一个值,从结果中可以看到,cloneType对concretePrototype进行深拷贝,深拷贝对自己的属性添加了值只会对自己的引用值进行改变,并没有对原始对象的引用值改变。深拷贝是无论是基本类型的值还是引用类型的内存地址都会拷贝一份。

  • 深拷贝基本原理图

image.png

克隆破坏单例模式

如果我们深拷贝的是原型对象是单例对象,那么深拷贝就会破坏单例,实际上Spring为了防止单例被破坏,就有一个很简单的方法,禁止深拷贝的就可以。要么单例不实现Clenable接口,要么重写Object的clone方法,并在clone方法中返回单例对象。

原型模式的优缺点

优点

  • 性能高,使用java自带的原型模式可以比直接new上一个对象的操作和消耗的资源都小很多,对性能上有了很好的提升。

  • 代码流程简单, 原型模式的拷贝简化对象的创建的过程,可以直接修改现有的对象实例的值,达到对象复用的目的,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。

缺点

  • 每个对象都必须重写Object中clone方法,Java提供了Cloneable标识该对象可以被拷贝,但是必须覆盖 Object 的 clone 方法才能被拷贝 ;

  • 因为clone方法在类中,如果对已经存在的类进行改变,那么就需要修改类中的方法,则就违反了设计模式七大原则中的开闭原则(对添加开发,对修改关闭)

  • 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深拷贝、浅拷贝需要运用得当。

总结

  • 拷贝方式

    • 1.序列化 反序列化
    • 2.JsonObject
    • 3浅克隆加赋值
  • 浅拷贝

    继承Cloneable接口的都是浅克隆。

  • 深克隆

    • 序列化
    • 转JSON