写在前面:对于原型模式的介绍,网上有很多。但是给我的感觉,有些介绍并不是在单纯的介绍原型模式本身的意义,而是附加了其他的设计模式。至少我在看过很多文章之后,不仅未清晰的了解原型模式的本质,反而愈发感觉糊里糊涂。当然,这或许是我个人还没达到理解这种模式的水平。
本文关于原型模式的介绍,基本是出于我个人依据定义的理解。或许是正确的,或许存在谬误,欢迎大家留言指正讨论。
目录
首先看下原型模式的定义:
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
Prototype原型模式是一种创建型设计模式,Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。
一、模式定义分析
通过上面的定义,得到的关键词是:对象复制。那我们就从对象和复制展开来讨论。
1、实现对象复制之前先回顾一下以下知识点:
- 在Java中,对象实例的创建一般都要通过new关键字,需要通过构造函数来实例化。既然要复制,可能要避开这个限制。
- 在Java中,创建的对象除去基本数据类型的非包装类、字符串对象(在JDK7之前,待考证)等,绝大多数的对象都是在堆内存中创建的。复制,就需要在内存空间中创建出一个独立的对象出来。
- 在Java中,提供了语言级别的复制支持,那就是Cloneable接口。这个接口是个标记接口,里面并没有要实现的方法。因为所有的对象都隐式的继承自Object类,Object类中有一个clone方法。这个clone方法有2个限制:①方法修饰符是protected,无法在非继承类内部使用,所以要在重写时把修饰符改为public。②如果没有实现Cloneable接口而直接调用clone方法,会报错。
- 对象中既有基本数据类型的成员变量,也有可能有引用类型的成员变量,拷贝方式肯定有区别。所以需要了解浅拷贝和深拷贝的概念。
2、哪些场景下应该考虑使用复制的模式呢?
因为和复制做对比的,主要是通过new关键字来创建对象,所以来看下new可能会遇到的性能问题。
①当new对象需要大量繁琐的准备工作时,复制会提高性能。
示例场景
public Demo getDemoInstance() {
Object obj = prepareObject();// 创建对象前需要执行一些耗时操作
Demo demo = new Demo(obj);
}
public Demo(Object obj) {
// 解析配置、初始化环境、设置变量等等操作
}
②当需要多次重复的调用new关键字执行构造函数时,累积的时间损耗也是可以考虑的优化点。
for(int i=0; i<1000000; i++) {
Demo demo = new Demo(); // 每次都要新建对象,而new又比较耗时的时候
handle(demo);
}
③当一个对象创建耗时,单实例又存在并发安全问题时,也可以考虑复制一份。
写到这里忍不住想起ThreadLocal类,这个号称为每个线程拷贝一份副本的类是怎么实现的?深拷贝还是浅拷贝呢?拷贝时如果泛型里的类没有实现Cloneable接口会怎样呢?回头再去看看...
3、复制在哪里操作?需要注意什么?
- 尽量减少new对象的地方,如果系统中大量通过new创建的对象,那么复制也就没啥意义了
- 复制需要在用到原型类对象的地方来操作
二、代码实现:
1、原型类代码
public class Prototype implements Cloneable {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
2、调用类代码
public class PrototypeClient {
public static void main(String[] args) throws CloneNotSupportedException {
Prototype raw = new Prototype();
for (int i=0; i<1000; i++) {
Prototype clone = (Prototype) raw.clone();
System.out.println(clone);
}
}
}
三、补充说明:
综上,复制之前我们需要先有一个现成的对象,但是这个现成的对象数量不宜过多,否则就失去了复制的意义。原始对象可以考虑使用缓存的方式、单例模式等。