一、定义
原型模式(Prototype Pattern):创新型模式之一,用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
二、UML类图
三、角色职责
- 原型类(Prototype):声明一个克隆自己的接口。
- 具体原型类(Concrete Prototype):实现一个克隆自己的操作。
- 客户端(Client):客户端让一个原型对象克隆自己,从而创建一个新的对象。
四、代码实现
前言:举个栗子,我们现在创建了一个新的QQ号,我们想把我们老QQ号上的好友克隆到新的QQ号上。那么,常规代码如下:
先创建一个QQ好友类
@AllArgsConstructor
@Data
public class QQFriend {
/**
* 昵称
*/
private String nickname;
/**
* QQ号
*/
private String QQNo;
}
然后我们进行克隆
public class PrototypeTest {
public static void main(String[] args) {
QQFriend qqFriend = new QQFriend("奶油芝士蘑菇汤", "1706046690");
QQFriend clone = new QQFriend(qqFriend.getNickname(), qqFriend.getQQNo());
// 运行结果:false
System.out.println(qqFriend == clone);
}
}
这种写法,在我们每次创建新对象的时候都要获取原始对象的属性,效率很低,并且不能动态获取对象的运行时状态,如果原始对象的属性改变了,也需要改动代码。对此,我们可以使用原型模式。
原型模式又可分为浅拷贝和深拷贝,区别在于对引用数据类型的成员变量的拷贝。
-
浅拷贝:浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
将原先的QQ好友类实现Cloneable接口并重写clone()方法,再添加一个QQ好友的属性
@AllArgsConstructor @Data public class QQFriend implements Cloneable { /** * 昵称 */ private String nickname; /** * QQ号 */ private String QQNo; /** * QQ好友 */ private QQFriend qqFriend; public QQFriend(String nickname, String QQNo) { this.nickname = nickname; this.QQNo = QQNo; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
查看运行结果
public class PrototypeTest { public static void main(String[] args) throws Exception { QQFriend qqFriend = new QQFriend("奶油芝士蘑菇汤", "1706046690",new QQFriend("初见","1415478419")); QQFriend clone = (QQFriend)qqFriend.clone(); // 运行结果:true System.out.println(qqFriend.getQqFriend() == clone.getQqFriend()); } }
通过运行结果可以发现,浅拷贝通过clone()方法拷贝了一个新对象,但是这个新对象的好友属性地址还是指向原始对象的好友属性地址,如果原始对象的属性改变了,那新对象的属性也会发生改变。
-
深拷贝:深拷贝会创造一个一摸一样的对象,新对象和原对象不共享内存,修改新对象不会改变原对象。
我们只需要将QQ好友类的克隆属性进行修改,将QQ好友类的QQ好友属性在克隆时再次进行克隆。
@AllArgsConstructor @Data public class QQFriend implements Cloneable { /** * 昵称 */ private String nickname; /** * QQ号 */ private String QQNo; /** * QQ好友 */ private QQFriend qqFriend; public QQFriend(String nickname, String QQNo) { this.nickname = nickname; this.QQNo = QQNo; } @Override protected Object clone() throws CloneNotSupportedException { QQFriend qqFriend = (QQFriend)super.clone(); if(this.getQqFriend() != null) qqFriend.setQqFriend((QQFriend)this.getQqFriend().clone()); return qqFriend; } }
查看运行结果
public class PrototypeTest { public static void main(String[] args) throws Exception { QQFriend qqFriend = new QQFriend("奶油芝士蘑菇汤", "1706046690",new QQFriend("初见","1415478419")); QQFriend clone = (QQFriend)qqFriend.clone(); // 运行结果:false System.out.println(qqFriend.getQqFriend() == clone.getQqFriend()); } }
浅拷贝通过clone()方法拷贝了一个新对象时,其对象的好友属性也会拷贝成一个全新属性。这时,我们就完成了深拷贝。
-
深拷贝(序列化)
我们还可以通过序列化的方式完成深拷贝。我们在QQFriend类中实现序列化接口。@AllArgsConstructor @Data public class QQFriend implements Serializable { /** * 昵称 */ private String nickname; /** * QQ号 */ private String QQNo; /** * QQ好友 */ private QQFriend qqFriend; public QQFriend(String nickname, String QQNo) { this.nickname = nickname; this.QQNo = QQNo; } public Object cloneBySerializable() throws Exception { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); QQFriend qqFriend = (QQFriend) ois.readObject(); bos.close(); oos.close(); bis.close(); ois.close(); return qqFriend; } }
运行结果
public class PrototypeTest { public static void main(String[] args) throws Exception { QQFriend qqFriend = new QQFriend("奶油芝士蘑菇汤", "1706046690",new QQFriend("初见","1415478419")); QQFriend clone = (QQFriend)qqFriend.cloneBySerializable(); // 运行结果:false System.out.println(qqFriend.getQqFriend() == clone.getQqFriend()); } }
五、源码分析
在Spring中,配置bean时,scope属性的prototype值就是使用的原型模式。
我们直接进入AbstractBeanFactory类的doGetBean()方法。
进入到doGetBean()方法可以发现,spring对参数进行了判断,对应调用createBean创建了原型模式的对象
六、优缺点分析
优点:
- 使用原型模式复用的方式创建实例对象,比使用构造函数重新创建对象性能要高。
- 原型模式可以简化创建的过程,可以直接修改现有的对象实例的值 , 达到复用的目的。
缺点:
- Java 中提供了 cloneable 标识该对象可以被拷贝,但是必须覆盖 Object 的 clone 方法才能被拷贝 。
七、适用场景
当类中定义的构造函数复杂或在内存中循环创建了很多该实例对象,就可以使用原型模式复用不用的对象,用于创建新对象。
八、总结
其实原型模式说白了就是克隆,用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。