这是我参与新手入门的第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
其实指向的是同一个地址,那么一个修改了,另一个也将发生变化
深克隆
浅克隆往往不能满足我们的实际需要,我们想要的是两个对象的完全隔离,这样就需要做一个深克隆。
其实也很简单,对于基本类型不用管,对于引用类型,我们不希望仅仅是克隆它的地址引用,那么在克隆的时候,我们主动去克隆一份不就可以了。
基于浅克隆的例子加以修改:
为引用类型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))
复制代码
达到深克隆效果
总结
在使用原型模式的时候需要注意是深克隆还是浅克隆,避免造成安全隐患。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END