原型模式
原型模式(Prototype Pattern)指原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象,属于创
建型模式;
原型模式的核心在于复制原型对象。以系统中已存在的一个对象为原型,直接基于内存二进制流进行复制,不需要
再精力耗时的对象初始化过程(不调用构造函数),性能提升很多。当对象的构造过程比较耗时时,可以把当前系统
已存在的对象作为原型,对其进行复制(一般是基于二进制流的复制),躲避初始化过程,使得新对象的创建时间大大缩短;
原型模式类图
IPrototype 定义克隆的方法 类似于JDK自带的Cloneable
1 2 3
| public interface IPrototype<T> { T clone(); }
|
ConcretePrototype 具体的要克隆的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public class ConcretePrototype implements IPrototype {
private int age; private String name;
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getName() { return name; }
public void setName(String name) { this.name = name; } @Override public ConcretePrototype clone() { ConcretePrototype concretePrototype = new ConcretePrototype(); concretePrototype.setAge(this.age); concretePrototype.setName(this.name); return concretePrototype; }
@Override public String toString() { return "ConcretePrototype{" + "age=" + age + ", name='" + name + '\'' + '}'; } }
|
Client 客户端 测试
1 2 3 4 5 6 7 8 9 10 11 12
| public static void main(String[] args) { ConcretePrototype prototype = new ConcretePrototype(); prototype.setAge(18); prototype.setName("Tom"); System.out.println(prototype);
ConcretePrototype cloneType = prototype.clone(); System.out.println(cloneType); System.err.println(cloneType == prototype); }
|
运行结果:
1 2 3
| ConcretePrototype{age=18, name='Tom'} ConcretePrototype{age=18, name='Tom'} false
|
实现JDK Cloneable的克隆对象写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public class ConcretePrototype1 implements Cloneable {
private int age; private String name;
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override public ConcretePrototype1 clone() { Object clone = null; try { clone = super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); System.err.println("Clone Error!"); } return (ConcretePrototype1) clone; }
@Override public String toString() { return "ConcretePrototype{" + "age=" + age + ", name='" + name + '\'' + '}'; } }
|
输出结果同上;以上的两个属性都是基本数据类型和String,并没有引用类型,下面我们添加一个引用类型的属性测试以下👇👇
ConcretePrototype添加一个List类型属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| public class ConcretePrototype implements IPrototype {
private int age; private String name; private List<String> hobbies;
public List<String> getHobbies() { return hobbies; }
public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override public ConcretePrototype clone() { ConcretePrototype concretePrototype = new ConcretePrototype(); concretePrototype.setAge(this.age); concretePrototype.setName(this.name); concretePrototype.setHobbies(this.hobbies); return concretePrototype; }
@Override public String toString() { return "ConcretePrototype{" + "age=" + age + ", name='" + name + '\'' + ", hobbies=" + hobbies + '}'; } }
|
SETTER方法实现浅克隆
浅克隆也叫浅拷贝:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public static void main(String[] args) { ConcretePrototype1 prototype = new ConcretePrototype1(); prototype.setAge(18); prototype.setName("Tom"); ArrayList<String> strings = Lists.newArrayList("数学", "英语"); prototype.setHobbies(strings); System.out.println(prototype);
ConcretePrototype1 cloneType = prototype.clone(); cloneType.getHobbies().add("语文"); System.out.println(cloneType); System.out.println(prototype); System.err.println(cloneType == prototype); System.err.println(cloneType.getHobbies() == prototype.getHobbies()); } 输出结果: ConcretePrototype1{age=18, name='Tom', hobbies=[数学, 英语]} ConcretePrototype1{age=18, name='Tom', hobbies=[数学, 英语, 语文]} ConcretePrototype1{age=18, name='Tom', hobbies=[数学, 英语, 语文]} false
true
|
通过内存字节流”克隆”对象,实现深克隆
深克隆也叫深拷贝:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| public class ConcretePrototypeDeep implements Cloneable,Serializable {
private int age; private String name; private List<String> hobbies;
public List<String> getHobbies() { return hobbies; }
public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public ConcretePrototypeDeep deepClone() { try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(this); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); return (ConcretePrototypeDeep) objectInputStream.readObject(); } catch (IOException ioException) { ioException.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return null; }
@Override public String toString() { return "ConcretePrototype{" + "age=" + age + ", name='" + name + '\'' + ", hobbies=" + hobbies + '}'; } }
|
输出结果:
1 2 3 4 5
| ConcretePrototype{age=18, name='Tom', hobbies=[数学, 英语]} ConcretePrototype{age=18, name='Tom', hobbies=[数学, 英语, 语文]} ConcretePrototype{age=18, name='Tom', hobbies=[数学, 英语]} false false
|
在Java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现,也可以通过序列化(Serialization)等方式来实现。
要实现深拷贝,必须实现Cloneable接口,去重写clone方法,否则会抛出CloneNotSupportedException异常,
但是如果对象中包含很多引用类型的属性,这样去覆盖clone方法其实是很麻烦的,可以优先使用序列化的方式实现!
克隆破坏单例模式
如果我们克隆的目标的对象是单例对象,这便意味着,深克隆会破坏单例。解决以上问题的思路:
- 禁止深克隆
- 在单例对象的getInstance方法,返回当前对象,而不是去新创建一个对象或者通过内存字节流等方法生成对象
原型模式在Java中的应用
ArrayList底层是基于数组结果的,它的动态扩容过程是创建一个新的数组,并把数组中的元素拷贝过去,用新的数组来继续存放元素;
在创建新数组的过程中便使用了原型模式。
java.util.ArrayList.clone
1 2 3 4 5 6 7 8 9 10 11
| public Object clone() { try { ArrayList<?> v = (ArrayList<?>) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { throw new InternalError(e); } }
|
java.util.Arrays.copyOf(T[], int)
1 2 3
| public static <T> T[] copyOf(T[] original, int newLength) { return (T[]) copyOf(original, newLength, original.getClass()); }
|
java.util.Arrays.copyOf(U[], int, java.lang.Class<? extends T[]>)
1 2 3 4 5 6 7 8 9
| public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { @SuppressWarnings("unchecked") T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }
|
最终还是System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));这个方法来完成数组的拷贝!
像我们使用的
com.alibaba.fastjson.JSON#parseObject
org.springframework.beans.BeanUtils#copyProperties(java.lang.Object, java.lang.Object)`
等都是原型模式;
原型模式适用场景
- 类初始化消耗资源过多
- new一个对象需要很多繁琐的过程(数据准备,访问权限等)
- 构造函数比较复杂
- 循环体内产生大量对象时
原型模式的优点
- Java自带的原型模式是基于内存二进制流的复制,在性能上比直接创建一个对象更加优良
- 可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,
以便在需要的时候使用,可辅助实现撤销操作。
原型模式的缺点
- 需要为每一个类都配置一个 clone 方法
- clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则
- 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,
实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当