设计模式-原型模式-爱代码爱编程
定义: 原型模式(Prototype Design Pattern)用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
原型模式主要解决的问题
-
如果创建对象的成本比较大,比如对象中的数据是经过复杂计算才能得到,或者需要从 RPC 接口或者数据库等比较慢的 IO 中获取,这种情况我们就可以使用原型模式,从其他已有的对象中进行拷贝,而不是每次都创建新对象,进行一些耗时的操作。
原型模式原理
原型模式包含如下角色:
-
抽象原型类(Prototype):规定了具体原型对象必须实现的clone()方法(Cloneable 接口)
-
具体原型类(ConcretePrototype):实现了抽象原型类的clone()方法,它是可以被复制的对象(实现 Cloneable 接口的子实现类)
-
客户类(Client):使用具体原型类中的clone()方法来复制新对象
深克隆与浅克隆
根据在复制原型对象的同时是否复制包含在原型对象中引用类型的成员变量 这个条件,原型模式的克隆机制分为两种,即浅克隆(Shallow Clone)和深克隆(Deep Clone)
1) 什么是浅克隆
创建一个新对象,对象中属性和原来对象的属性完全相同,对于引用类型属性仍指向原有属性所指向的内存地址
2) 什么是深克隆
创建一个新对象,属性中引用类型也会被克隆,不再指向原来属性所指向的内存地址
Java中的Object类中提供了 clone()
方法来实现浅克隆。需要注意的是要想实现克隆的Java类必须实现一个标识接口 Cloneable ,来表示这个Java类支持被复制。
3) 浅克隆代码实现:
public class ConcretePrototype implements Cloneable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected ConcretePrototype clone() throws CloneNotSupportedException {
return (ConcretePrototype)super.clone();
}
}
测试
public static void main(String[] args) throws CloneNotSupportedException {
// 创建原型对象
ConcretePrototype c1 = new ConcretePrototype();
// 复制原型对象
ConcretePrototype c2 = c1.clone();
c1.setName("大哥");
c2.setName("二哥");
c1.show(); // 姓名: 大哥
c2.show(); // 姓名: 二哥
}
浅克隆存在的问题
在 ConcretePrototype 类中添加一个 Person 类型对象属性
public class ConcretePrototype implements Cloneable {
private String name;
// 引用类型对象
private Person person;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
@Override
protected ConcretePrototype clone() throws CloneNotSupportedException {
return (ConcretePrototype)super.clone();
}
}
class Person {
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试
public static void main(String[] args) throws CloneNotSupportedException {
// 创建原型对象
ConcretePrototype c1 = new ConcretePrototype();
Person p1 = new Person();
p1.setName("大哥");
c1.setPerson(p1);
// 复制原型对象
ConcretePrototype c2 = c1.clone();
Person p2 = c2.getPerson();
p2.setName("二哥");
c1.show(); // 姓名: 二哥
c2.show(); // 姓名: 二哥
}
原因:对于引用类型属性仍指向原有属性所指向的内存地址,原型类中的 Person 对象和克隆类中的对象是同一个对象,当修改克隆类的person 对象时也会修改原型类中的 person 对象
4) 深克隆代码实现
两种实现方式
-
重写 clone() 方法
-
序列化对象
1. 重写 clone() 方法
在 Person 类中实现 Cloneable 接口,重写 clone() 方法
public class Person implements Cloneable {
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Person clone() throws CloneNotSupportedException {
return (Person)super.clone();
}
}
重写 ConcretePrototype 类中的 clone() 方法
@Override
protected ConcretePrototype clone() throws CloneNotSupportedException {
ConcretePrototype clone = (ConcretePrototype) super.clone();
clone.person = person.clone();
return clone;
}
测试
public static void main(String[] args) throws CloneNotSupportedException {
// 创建原型对象
ConcretePrototype c1 = new ConcretePrototype();
Person p1 = new Person();
p1.setName("大哥");
c1.setPerson(p1);
// 复制原型对象
ConcretePrototype c2 = c1.clone();
Person p2 = c2.getPerson();
p2.setName("二哥");
c1.show(); // 姓名: 大哥
c2.show(); // 姓名: 二哥
}
2. 序列化对象(推荐)
首先将 ConcretePrototype、Person 类实现 Serializable
测试
public static void main(String[] args) throws CloneNotSupportedException {
ConcretePrototype c1 = new ConcretePrototype();
Person p1 = new Person();
p1.setName("大哥");
c1.setPerson(p1);
// 创建对象序列化输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("c.txt"));
// 将c1对象写到文件中
oos.writeObject(c1);
oos.close();
// 创建对象序列化输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("c.txt"));
// 读取对象
ConcretePrototype c2 = (ConcretePrototype) ois.readObject();
Person p2 = c2.getPerson();
p2.setName("二哥");
c1.show(); // 姓名: 大哥
c2.show(); // 姓名: 二哥
}
原型模式总结
原型模式的优点
-
当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程, 通过复制一个已有实例可以提高新实例的创建效率.
比如,在 AI 系统中,我们经常需要频繁使用大量不同分类的数据模型文件,在对这一类文件建立对象模型时,不仅会长时间占用 IO 读写资源,还会消耗大量 CPU 运算资源,如果频繁创建模型对象,就会很容易造成服务器 CPU 被打满而导致系统宕机。通过原型模式我们可以很容易地解决这个问题,当我们完成对象的第一次初始化后,新创建的对象便使用对象拷贝(在内存中进行二进制流的拷贝),虽然拷贝也会消耗一定资源,但是相比初始化的外部读写和运算来说,内存拷贝消耗会小很多,而且速度快很多
-
原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构(具体工厂对应具体产品),而原型模式就不需要这样,原型模式的产品复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品.
-
可以使用深克隆的方式保存对象状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用,比如恢复到某一历史状态,可以辅助实现撤销操作.
在某些需要保存历史状态的场景中,比如,聊天消息、上线发布流程、需要撤销操作的程序等,原型模式能快速地复制现有对象的状态并留存副本,方便快速地回滚到上一次保存或最初的状态,避免因网络延迟、误操作等原因而造成数据的不可恢复。
原型模式缺点
-
需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时需要修改源代码,违背了开闭原则。
使用场景
原型模式常见的使用场景有以下六种。
-
资源优化场景。也就是当进行对象初始化需要使用很多外部资源时,比如,IO 资源、数据文件、CPU、网络和内存等。
-
复杂的依赖场景。 比如,F 对象的创建依赖 A,A 又依赖 B,B 又依赖 C……于是创建过程是一连串对象的 get 和 set。
-
性能和安全要求的场景。 比如,同一个用户在一个会话周期里,可能会反复登录平台或使用某些受限的功能,每一次访问请求都会访问授权服务器进行授权,但如果每次都通过 new 产生一个对象会非常烦琐,这时则可以使用原型模式。
-
同一个对象可能被多个修改者使用的场景。 比如,一个商品对象需要提供给物流、会员、订单等多个服务访问,而且各个调用者可能都需要修改其值时,就可以考虑使用原型模式。
-
需要保存原始对象状态的场景。 比如,记录历史操作的场景中,就可以通过原型模式快速保存记录。
参考:https://blog.csdn.net/m0_60117382/article/details/123583312