原型模式 Prototype Pattern
- 属于创建型模式
- 是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,调用者不需要知道任何创建细节,不调用构造函数
- 关键点:不通过 new 关键字,而是通过方法去创建对象
原型模式的使用场景
- 类初始化消耗资源较多
- new 产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
- 构造函数比较复杂
- 循环体中生产大量对象
通过硬编码复制对象
编写比较麻烦
java
@Data
public class User {
private Long id;
private String name;
private String pwd;
private String addr;
private String mobile;
private int age;
// ... 属性非常多 ...
/**
* 硬编码复制
*/
public User copy() {
User user = new User();
user.setId(this.getId());
user.setName(this.getName());
user.setPwd(this.getPwd());
user.setAddr(this.getAddr());
user.setMobile(this.getMobile());
user.setAge(this.getAge());
// ...这里还有很多属性set...
return user;
}
}
通过反射复制对象
本质也是 set、get
java
public static Object copy(Object prototype) {
Class<?> clazz = prototype.getClass();
Object returnValue;
try {
returnValue = clazz.newInstance();
// 获取所有属性, 并设置可访问
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
field.set(returnValue, field.get(prototype));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return returnValue;
}
浅拷贝复制对象
浅拷贝的特点
- 对于基本类型是按值复制,不会因为原对象的基本类型值的改变而影响到拷贝后的对象
- 对于引用类型,prototype 和 clone 对象引用同一个引用类型对象(复制引用对象的内存地址)
浅拷贝存在的问题
- 修改 clone[prototype] 引用的对象后,会影响到 prototype[clone]
java
@Data
public class ConcretePrototype implements Cloneable {
private String name;
private int age;
private List<String> hobbies;
/**
* 实现 Cloneable 接口, 底层使用字节码进行克隆
*/
@Override
public ConcretePrototype clone() {
try {
// TODO: copy mutable state here, so the clone can't change the internals of the original
return (ConcretePrototype) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
测试及结果
修改 prototype 的 hobbies 后,clone 的 hobbies 也被修改了
java
public static void main(String[] args) {
ShallowConcretePrototype prototype = new ShallowConcretePrototype();
prototype.setAge(20);
prototype.setName("yes");
ArrayList<String> hobbies = new ArrayList<>();
hobbies.add("书法");
hobbies.add("象棋");
prototype.setHobbies(hobbies);
ShallowConcretePrototype clone = prototype.clone();
System.out.println("prototype == clone : " + (prototype == clone));
prototype.setAge(25);
System.out.println("==== 修改基本类型 age ====");
System.out.println(prototype);
System.out.println(clone);
prototype.hobbies.add("游戏");
System.out.println("prototype.hobbies == clone.hobbies : " + (prototype.hobbies == clone.hobbies));
System.out.println("==== 修改引用类型 hobbies ====");
System.out.println(prototype.hobbies);
System.out.println(clone.hobbies);
System.out.println("prototype.hobbies == clone.hobbies : " + (prototype.hobbies == clone.hobbies));
}
流实现深拷贝复制对象
java
@Data
public class DeepConcretePrototype implements Serializable
, Cloneable {
private String name;
private int age;
private List<String> hobbies;
@Override
public DeepConcretePrototype clone(){
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (DeepConcretePrototype)ois.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
测试及结果
java
public static void main(String[] args) {
DeepConcretePrototype prototype = new DeepConcretePrototype();
prototype.setAge(20);
prototype.setName("yes");
ArrayList<String> hobbies = new ArrayList<>();
hobbies.add("书法");
hobbies.add("象棋");
prototype.setHobbies(hobbies);
DeepConcretePrototype clone = prototype.clone();
System.out.println("prototype == clone : " + (prototype == clone));
prototype.setAge(25);
System.out.println("==== 修改基本类型 age ====");
System.out.println(prototype);
System.out.println(clone);
prototype.hobbies.add("游戏");
System.out.println("prototype.hobbies == clone.hobbies : " + (prototype.hobbies == clone.hobbies));
System.out.println("==== 修改引用类型 hobbies ====");
System.out.println(prototype.hobbies);
System.out.println(clone.hobbies);
System.out.println("prototype.hobbies == clone.hobbies : " + (prototype.hobbies == clone.hobbies));
}
json 字符串实现深拷贝复制对象
todo
防止单例被原型模式破坏
1、单例类不实现 cloneable 接口
2、实现 cloneable,但是 clone 方法直接返回单例对象
原型模式在源码中的应用
ArrayList
HashMap
总结
优点:
- 性能优良,Java 自带的原型模式是基于二进制流的拷贝,比直接 new 一个对象性能上提升了许多
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,简化了创建过程
缺点:
- 必须匹配克隆方法
- 当对已有类进行改造的时候,需要修改代码,违反了开闭原则
- 深拷贝、浅拷贝需要运用得当