原型(Prototype)模式的定义:用一个已经存在的对象实例作为原型,通过复制该原型对象来创建一个和原型对象相同或相似的新对象。属于建造型模式。
原型模式的结构:原型模式主要包含3种角色。
- 抽象原型:规定了具体原型对象必须实现的接口。
- 具体原型:实现抽象原型类的 clone() 方法,它是可被复制的原型对象。
- 访问类:使用具体原型类中的 clone() 方法来复制出新的对象。
原型模式的实现:原型模式的克隆分为浅克隆和深克隆。
- 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原来对象的属性所指向的对象的内存地址。
- 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
Java中的Object类提供了浅克隆的clone()方法,Cloneable 接口作为抽象原型,具体原型类只要实现 Cloneable 接口就可实现对象的浅克隆。
//具体原型
public class Prototype implements Cloneable{
private String id;
private String name;
private List<String> address;
@Override
public Prototype clone() throws CloneNotSupportedException {
return (Prototype) super.clone();
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getAddress() {
return address;
}
public void setAddress(List<String> address) {
this.address = address;
}
@Override
public String toString() {
return "prototype:{id:'"+id+"',name:"+name+"',address:'"+address+"'}";
}
}
//访问测试类
public class PrototypeTest {
public static void main(String[] args) throws CloneNotSupportedException {
Prototype prototype = new Prototype();
prototype.setId("1");
prototype.setName("zhangsan");
List<String> address = new ArrayList<>();
address.add("北京");
address.add("上海");
prototype.setAddress(address);
System.out.println(prototype.toString());
//复制对象
Prototype clonePrototype = prototype.clone();
clonePrototype.getAddress().add("广州");
System.out.println(clonePrototype.toString());
System.out.println(prototype.toString());
}
}
通过运行上边的代码,发现修改克隆对象的address属性,原对象也会随之改变。证实了浅克隆时对于非基本类型属性,仍指向原型对象的属性所对应的内存地址。
深克隆的实现:
//具体原型
public class Prototype implements Cloneable{
private String id;
private String name;
private List<String> address;
public Prototype deepClone() throws CloneNotSupportedException {
Prototype prototype = (Prototype) super.clone();
List<String> address = prototype.getAddress();
List<String> cloneAddress = (List<String>) ((ArrayList)address).clone();
prototype.setAddress(cloneAddress);
return prototype;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getAddress() {
return address;
}
public void setAddress(List<String> address) {
this.address = address;
}
@Override
public String toString() {
return "prototype:{id:'"+id+"',name:"+name+"',address:'"+address+"'}";
}
}
//访问测试类
public class PrototypeTest {
public static void main(String[] args) throws CloneNotSupportedException {
Prototype prototype = new Prototype();
prototype.setId("1");
prototype.setName("zhangsan");
List<String> address = new ArrayList<>();
address.add("北京");
address.add("上海");
prototype.setAddress(address);
System.out.println(prototype.toString());
//深度克隆对象
Prototype clonePrototype = prototype.deepClone();
clonePrototype.getAddress().add("广州");
System.out.println(clonePrototype.toString());
System.out.println(prototype.toString());
}
}
上面的写法虽然实现了深克隆,但是如果prototype对象中包含各种集合类型或其他对象类型,就需要做各种单独的克隆处理,针对这种硬编码,可以采用序列化的方式实现对象的深克隆。
//具体原型
public class Prototype implements Cloneable, Serializable {
private String id;
private String name;
private List<String> address;
public Prototype deepClone() throws IOException, ClassNotFoundException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(this);
InputStream is = new ByteArrayInputStream(os.toByteArray());
ObjectInputStream ois = new ObjectInputStream(is);
return (Prototype) ois.readObject();
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getAddress() {
return address;
}
public void setAddress(List<String> address) {
this.address = address;
}
@Override
public String toString() {
return "prototype:{id:'"+id+"',name:"+name+"',address:'"+address+"'}";
}
}
//访问测试类
public class PrototypeTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Prototype prototype = new Prototype();
prototype.setId("1");
prototype.setName("zhangsan");
List<String> address = new ArrayList<>();
address.add("北京");
address.add("上海");
prototype.setAddress(address);
System.out.println(prototype.toString());
//复制对象
Prototype clonePrototype = prototype.deepClone();
clonePrototype.getAddress().add("广州");
System.out.println(clonePrototype.toString());
System.out.println(prototype.toString());
}
}
原型模式的优点:Java自带的原型模式基于内存二进制流的复制,比通过构造方法创建对象性能更快。
原型模式的缺点:
- 需要为每一个类都重写clone方法
- clone方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
原型模式应用场景:
- 对象之间相同或相似,即只是个别的几个属性不同的时候。
- 创建对象成本较大,例如初始化时间长,占用CPU太多,或者占用网络资源太多等,需要优化资源。
- 创建一个对象需要繁琐的数据准备或访问权限等,需要提高性能或者提高安全性。
- 系统中大量使用该类对象,且各个调用者都需要给它的属性重新赋值。
在Spring中,原型模式应用的非常广泛,例如 scope='prototype'、JSON.parseObject() 等都是原型模式的实现。