09.创建型 - 原型模式 (Prototype Pattern)
原型模式 (Prototype Pattern)
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。
创建/复用大量的对象, 且对象的创建过程较复杂, 内部有复杂的依赖 部分可以共用或者不共用. 可以将创建/克隆的过程委托实例对象本身进行操作.
浅克隆
@Data
public class PrototypePattern implements Cloneable {
String baseString;
Person refPerson;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Data
static class Person{
String name;
}
public static void main(String[] args) throws CloneNotSupportedException {
PrototypePattern o1 = new PrototypePattern();
o1.baseString = "test";
o1.refPerson = new Person();
PrototypePattern o2 = (PrototypePattern) o1.clone();
System.out.println("浅克隆 o1 == o2: "+ (o1 == o2) );
System.out.println("浅克隆 o1.baseString == o2.baseString: "+ (o1.baseString == o2.baseString) );
System.out.println("浅克隆 o1.refPerson == o2.refPerson: "+ (o1.refPerson == o2.refPerson) );
/**
浅克隆 o1 == o2: false
浅克隆 o1.baseString == o2.baseString: true
浅克隆 o1.refPerson == o2.refPerson: true
**/
}
}
对于引用类型:
clone 方法是浅拷贝,对象内属性引用的对象只会拷贝引用地址,而不会将引用的对象重新分配内存
对于基本类型:
基本数据类型在赋值的时候,永远都是传值,而不是传引用
double m = 4; // 此时, 给变量m,开辟了8bytes的内存空间存储
double n = m; // 此时也会,给变量n,开辟新的8bytes内存空间
基于Java的 Object::clone() 方法: JVM会进行内存操作直接 二进制拷贝原始数据流, 简单粗暴, 不会有其他更多的复杂操作(调用构造,初始化等等), 速度远远快于实例化操作;
Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
The general intent is that, for any object x, the expression:
x.clone() != xwill be true, and that the expression:x.clone().getClass() == x.getClass()will be true
but these are not absolute requirements. While it is typically the case that:
x.clone().equals(x)will be true, this is not an absolute requirement.
java.lang.Object#clone
/**
* Creates and returns a copy of this object. The precise meaning
* of "copy" may depend on the class of the object. The general
* intent is that, for any object {@code x}, the expression:
* <blockquote>
* <pre>
* x.clone() != x</pre></blockquote>
* will be true, and that the expression:
* <blockquote>
* <pre>
* x.clone().getClass() == x.getClass()</pre></blockquote>
* will be {@code true}, but these are not absolute requirements.
* While it is typically the case that:
* <blockquote>
* <pre>
* x.clone().equals(x)</pre></blockquote>
* will be {@code true}, this is not an absolute requirement.
* <p>
* By convention, the returned object should be obtained by calling
* {@code super.clone}. If a class and all of its superclasses (except
* {@code Object}) obey this convention, it will be the case that
* {@code x.clone().getClass() == x.getClass()}.
* <p>
* By convention, the object returned by this method should be independent
* of this object (which is being cloned). To achieve this independence,
* it may be necessary to modify one or more fields of the object returned
* by {@code super.clone} before returning it. Typically, this means
* copying any mutable objects that comprise the internal "deep structure"
* of the object being cloned and replacing the references to these
* objects with references to the copies. If a class contains only
* primitive fields or references to immutable objects, then it is usually
* the case that no fields in the object returned by {@code super.clone}
* need to be modified.
*
* @implSpec
* The method {@code clone} for class {@code Object} performs a
* specific cloning operation. First, if the class of this object does
* not implement the interface {@code Cloneable}, then a
* {@code CloneNotSupportedException} is thrown. Note that all arrays
* are considered to implement the interface {@code Cloneable} and that
* the return type of the {@code clone} method of an array type {@code T[]}
* is {@code T[]} where T is any reference or primitive type.
* Otherwise, this method creates a new instance of the class of this
* object and initializes all its fields with exactly the contents of
* the corresponding fields of this object, as if by assignment; the
* contents of the fields are not themselves cloned. Thus, this method
* performs a "shallow copy" of this object, not a "deep copy" operation.
* <p>
* The class {@code Object} does not itself implement the interface
* {@code Cloneable}, so calling the {@code clone} method on an object
* whose class is {@code Object} will result in throwing an
* exception at run time.
*
* @return a clone of this instance.
* @throws CloneNotSupportedException if the object's class does not
* support the {@code Cloneable} interface. Subclasses
* that override the {@code clone} method can also
* throw this exception to indicate that an instance cannot
* be cloned.
* @see java.lang.Cloneable
*/
@IntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;
Java Cloneable 接口的作用
- Cloneable接口是标记型接口,内部没有方法和属性
- 实现 Cloneable 接口来表示该类可以被克隆,才可以调用Object.clone() 方法对该类的实例进行按字段复制。
- 如果在没有实现Cloneable 接口的实例上调用Object.clone() 方法,则会抛出 CloneNotSupportedException 异常。
深克隆 基于序列化
@Data
public class PrototypePattern implements Cloneable, Serializable {
String baseString;
Person refPerson;
@Data
static class Person implements Serializable{
String name;
}
public static void main(String[] args) throws Exception {
PrototypePattern o1 = new PrototypePattern();
o1.baseString = "test";
o1.refPerson = new Person();
//创建对象序列化输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("o1.data"));
//将o1对象写到文件中
oos.writeObject(o1);
oos.close();
//创建对象序列化输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("o1.data"));
//读取对象
PrototypePattern o2 = (PrototypePattern) ois.readObject();
System.out.println("深克隆 o1 == o2: "+ (o1 == o2) );
System.out.println("深克隆 o1.baseString == o2.baseString: "+ (o1.baseString == o2.baseString) );
System.out.println("深克隆 o1.refPerson == o2.refPerson: "+ (o1.refPerson == o2.refPerson) );
/**
深克隆 o1 == o2: false
深克隆 o1.baseString == o2.baseString: false
深克隆 o1.refPerson == o2.refPerson: false
**/
}
}
Java Serializable 接口的作用
- Serializable 接口是标记型接口,内部没有方法和属性
- ObjectOutPutStream 在序列化的时候,会判断对象的类型,如果不是字符串、数组、枚举、Serializable 的实例,会抛出 NotSerializableException
原型模式总结
原型模式优缺点
原型模式优点
-
当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程, 通过复制一个已有实例可以提高新实例的创建效率.
比如,在 AI 系统中,我们经常需要频繁使用大量不同分类的数据模型文件,在对这一类文件建立对象模型时,不仅会长时间占用 IO 读写资源,还会消耗大量 CPU 运算资源,如果频繁创建模型对象,就会很容易造成服务器 CPU 被打满而导致系统宕机。
通过原型模式我们可以很容易地解决这个问题,当我们完成对象的第一次初始化后,新创建的对象便使用对象拷贝(在内存中进行二进制流的拷贝),虽然拷贝也会消耗一定资源,但是相比初始化的外部读写和运算来说,内存拷贝消耗会小很多,而且速度快很多 -
原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构(具体工厂对应具体产品),而原型模式就不需要这样,原型模式的产品复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品.
-
可以使用深克隆的方式保存对象状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用,比如恢复到某一历史状态,可以辅助实现撤销操作.
在某些需要保存历史状态的场景中,比如,聊天消息、上线发布流程、需要撤销操作的程序等,原型模式能快速地复制现有对象的状态并留存副本,方便快速地回滚到上一次保存或最初的状态,避免因网络延迟、误操作等原因而造成数据的不可恢复。
原型模式缺点
- 需要为每一个类配备一个克隆方法, 而且该克隆方法位于一个类的内部,当对已有的类进行改造时需要修改源代码, 违背了开闭原则.
使用场景
原型模式常见的使用场景有以下六种。
-
资源优化场景。也就是当进行对象初始化需要使用很多外部资源时,比如,IO 资源、数据文件、CPU、网络和内存等。(比如游戏可复用的资源对象, 地图块, 背景, 只有部分(方向,颜色)属性不一样 )
-
复杂的依赖场景。 比如,F 对象的创建依赖 A,A 又依赖 B,B 又依赖 C……于是创建过程是一连串对象的 get 和 set。
-
性能和安全要求的场景。 比如,同一个用户在一个会话周期里,可能会反复登录平台或使用某些受限的功能,每一次访问请求都会访问授权服务器进行授权,但如果每次都通过 new 产生一个对象会非常烦琐,这时则可以使用原型模式。
-
同一个对象可能被多个修改者使用的场景。 比如,一个商品对象需要提供给物流、会员、订单等多个服务访问,而且各个调用者可能都需要修改其值时,就可以考虑使用原型模式。
-
需要保存原始对象状态的场景。 比如,记录历史操作的场景中,就可以通过原型模式快速保存记录。

浙公网安备 33010602011771号