重学设计模式-原型模式

这是我参与新手入门的第3篇文章

前言

原型模式在日常开发中似乎很少提及到,但是当我学完后,简单翻了下jdk1.8的源码,发现很多我们非常熟悉的类中就使用了原型模式进行优化,比如我们最常用的ArrayList中,感兴趣的同学可以去看看。

什么是原型模式

定义

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

优点

  • Java自带的原型模式基于内存二进制流的复制,复制比new一个对象更加高效。
  • 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。

缺点

  • 需要给每一个类配置一个clone方法。
  • clone方法位于类的内部,对类改造,修改类的代码,违背开闭原则。
  • 深克隆时,当对象之间存在多重嵌套时,每一层都必须支持深克隆,实现会比较麻烦。

应用场景

  • 类初始化需要消耗非常多的资源,这个资源包括数据、硬件资源等
  • new一个对象需要非常繁琐的数据准备或访问权限
  • 一个对象需要提供给其他对象访问,而且各个调用者可能需要修改其值,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝

原型模式的实现

  • 实现Cloneable接口
  • 重写 clone()方法

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

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

浅克隆

我们看一下示例代码,有一个订单类Order,有两个属性,一个基本类型serialNumber,另一个引用类型Product

代码

  • 订单类
package com.yang.prototype;
import lombok.Data;
/**
 * Order 订单类 
 * 属性 serialNumber 基础类型 编码
 * 属性 Product      引用类型 产品
 */
@Data
public class Order implements Cloneable {
    int serialNumber = 1;
    Product product = new Product();

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
复制代码
  • 产品类
package com.yang.prototype;
import lombok.Data;
/**
 * Product 产品类
 * 属性 productName 产品名称
 */
@Data
public class Product {
    String productName = "iphone13";
}
复制代码

测试

package com.yang.prototype;
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Order order = new Order();
        System.out.println(order);

        Order orderClone = (Order) order.clone();
        System.out.println(orderClone);

        System.out.println(order == orderClone);
        System.out.println(order.getProduct() == orderClone.getProduct());
    }
}
复制代码

打印输出

Order(serialNumber=1, product=Product(productName=iphone13))
Order(serialNumber=1, product=Product(productName=iphone13))
false
true
复制代码

结果证明:克隆具有自己的地址,但是引用类型参数,克隆的是它的引用地址值,下面这个例子将更具体证明这一点。

例子

package com.yang.prototype;

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        
        Order order = new Order();
        System.out.println(order);

        Order orderClone = (Order) order.clone();
        orderClone.setSerialNumber(2);
        orderClone.getProduct().setProductName("iphone14");

        System.out.println(order);
        System.out.println(orderClone);

    }
}
复制代码

打印输出

Order(serialNumber=1, product=Product(productName=iphone13))
Order(serialNumber=1, product=Product(productName=iphone14))
Order(serialNumber=2, product=Product(productName=iphone14))
复制代码

我们对克隆对象操作,修改了基础类型serialNumber的值,原型order不会被影响,当我们修改引用类型product的值,原型order也随之改变了。

因为对原型order对象的克隆,其中的product对象克隆的是它的地址值,那么克隆对象和原型对象中的product其实指向的是同一个地址,那么一个修改了,另一个也将发生变化

image.png

深克隆

浅克隆往往不能满足我们的实际需要,我们想要的是两个对象的完全隔离,这样就需要做一个深克隆

其实也很简单,对于基本类型不用管,对于引用类型,我们不希望仅仅是克隆它的地址引用,那么在克隆的时候,我们主动去克隆一份不就可以了。

基于浅克隆的例子加以修改:
为引用类型Product添加克隆条件(实现Cloneable接口,重新clone()方法)
代码

  • 产品类
package com.yang.prototype;
import lombok.Data;
/**
 * Product 产品类
 * 属性 productName 产品名称
 */
@Data
public class Product implements Cloneable {
    String productName = "iphone11";

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
复制代码

修改Order克隆方法

package com.yang.prototype;
import lombok.Data;
/**
 * Order 订单类
 * 属性 serialNumber 基础类型 编码
 * 属性 Product     引用类型  商品
 */
@Data
public class Order implements Cloneable {
    int serialNumber = 1;
    Product product = new Product();

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Order order = (Order) super.clone();
        order.setProduct((Product) order.getProduct().clone());
        return order;
    }
}
复制代码

测试

package com.yang.prototype;
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {

        Order order = new Order();
        System.out.println(order);

        Order orderClone = (Order) order.clone();
        orderClone.setSerialNumber(2);
        orderClone.getProduct().setProductName("iphone14");

        System.out.println(order);
        System.out.println(orderClone);

    }
}
复制代码

打印输出

Order(serialNumber=1, product=Product(productName=iphone13))
Order(serialNumber=1, product=Product(productName=iphone13))
Order(serialNumber=2, product=Product(productName=iphone14))
复制代码

达到深克隆效果
image.png

总结

在使用原型模式的时候需要注意是深克隆还是浅克隆,避免造成安全隐患。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享