原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
基本介绍@H_404_5@
意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
-
何时使用:
- 当一个系统应该独立于它的产品创建,构成和表示时。
- 当要实例化的类是在运行时刻指定时,例如,通过动态装载。
- 为了避免创建一个与产品类层次平行的工厂类层次时。
- 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
-
关键代码:
- 实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。
- 原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。
-
应用实例:
- 细胞分裂。
- JAVA 中的 Object clone() 方法。
-
优点:
-
缺点:
-
使用场景:
注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。
概括@H_404_5@
意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
- 当一个系统应该独立于它的产品创建,构成和表示时。
- 当要实例化的类是在运行时刻指定时,例如,通过动态装载。
- 为了避免创建一个与产品类层次平行的工厂类层次时。
- 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
- 实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。
- 原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。
- 细胞分裂。
- JAVA 中的 Object clone() 方法。
注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。
基本介绍
- 原型模式(Prototype 模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
- 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
- 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()
- 形象的理解:孙大圣拔出猴毛, 变出其它孙大圣
原型模式原理结构图 - uml 类图:
原理结构图说明
- Prototype:原型类,声明一个克隆自己的接口
- ConcretePrototype:具体的原型类,实现一个克隆自己的操作
- Client:让一个原型对象克隆自己,从而创建一个新的对象(属性一样)
我的理解@H_404_5@
先备份缓存该对象的原型,在下一个请求时返回它的克隆。
应用实例@H_404_5@
克隆羊问题
现在有一只羊 tom,姓名为: tom,年龄为:1,颜色为:白色,请编写程序创建和 tom 羊 属性完全相同的 10 只羊。
使用传统方式
- 思路分析(图解)
代码演示:
package com.nemo.prototype;
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
//传统的方法
Sheep sheep = new Sheep("tom",1,"白色");
Sheep sheep2 = new Sheep(sheep.getName(),sheep.getAge(),sheep.getColor());
Sheep sheep3 = new Sheep(sheep.getName(),sheep.getColor());
Sheep sheep4 = new Sheep(sheep.getName(),sheep.getColor());
Sheep sheep5 = new Sheep(sheep.getName(),sheep.getColor());
//....
System.out.println(sheep);
System.out.println(sheep2);
System.out.println(sheep3);
System.out.println(sheep4);
System.out.println(sheep5);
//...
}
}
传统方式的优缺点:
- 优点是比较好理解,简单易操作。
- 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低
- 总是需要重新初始化对象,而不是动态地获得对象运行时的状态,不够灵活
- 改进的思路分析
思路:Java 中 Object 类是所有类的根类,Object 类提供了一个 clone()方法,该方法可以将一个 Java 对象复制一份,但是需要实现 clone 的 Java 类必须要实现一个接口 Cloneable,该接口表示该类能够复制且具有复制的能力 =>原型模式
注意:克隆接口和序列化接口都是标记接口,里面没有任何属性或者方法,所以称之为标记接口,只有实现了标记接口,才能使用 Object 的克隆方法。
使用原型模式
使用原型模式改进传统方式,让程序具有更高的效率和扩展性。
代码实现:
Sheep
package com.nemo.prototype.improve;
public class Sheep implements Cloneable {
private String name;
private int age;
private String color;
private String address = "蒙古羊";
public Sheep friend; //是对象,克隆是会如何处理,默认是浅拷贝
public Sheep(String name,int age,String color) {
super();
this.name = name;
this.age = age;
this.color = color;
}
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;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Sheep [name=" + name + ",age=" + age + ",color=" + color + ",address=" + address + "]";
}
//克隆该实例,使用默认的 clone 方法来完成
@Override
protected Object clone() {
// TODO Auto-generated method stub return sheep;
Sheep sheep = null; try {
sheep = (Sheep)super.clone();
} catch (Exception e) {
// TODO: handle exception System.out.println(e.getMessage());
}
}
}
Client
package com.nemo.prototype.improve;
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("原型模式完成对象的创建");
Sheep sheep = new Sheep("tom","白色");
sheep.friend = new Sheep("jack",2,"黑色");
Sheep sheep2 = (Sheep)sheep.clone(); //克隆Sheep sheep3 = (Sheep)sheep.clone(); //克隆Sheep sheep4 = (Sheep)sheep.clone(); //克隆Sheep sheep5 = (Sheep)sheep.clone(); //克隆
System.out.println("sheep2 =" + sheep2 + "sheep2.friend=" + sheep2.friend.hashCode());
System.out.println("sheep3 =" + sheep3 + "sheep3.friend=" + sheep3.friend.hashCode());
System.out.println("sheep4 =" + sheep4 + "sheep4.friend=" + sheep4.friend.hashCode());
System.out.println("sheep5 =" + sheep5 + "sheep5.friend=" + sheep5.friend.hashCode());
}
}
原型模式在 Spring 框架中源码分析@H_404_5@
- Spring 中原型 bean 的创建,就是原型模式的应用
- 代码分析+Debug 源码
深入讨论-浅拷贝和深拷贝@H_404_5@
浅拷贝的介绍
- 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
- 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值
- 前面我们克隆羊就是浅拷贝
- 浅拷贝是使用默认的 clone()方法来实现
sheep = (Sheep) super.clone();
深拷贝基本介绍
- 复制对象的所有基本数据类型的成员变量值
- 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝
-
深拷贝实现方式 1:重写 clone 方法来实现深拷贝
多重浅拷贝,就变为了深拷贝。将引用类型全部浅拷贝完,只剩下基本数据类型即可。
-
深拷贝实现方式 2:通过对象序列化实现深拷贝(推荐)
序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
可以联想到 xml 序列化,用 xml 来存储或传输对象
因为对象是复合引用类型的所以不好持久存储或传输,只能序列化,化为 xml 即可表示对象,继而传输
序列化深拷贝是将对象输出流读入对象,再利用字节数组输出流进行输出,对象输入流又利用字节数组输入流进行读入,封装成一个新的对象。在输出时,序列化会将对象的所有相关信息,包括其拥有的引用类型的对象一起打包输出(而不是只输出引用类型的地址) ,所以接收的时候我们可以得到一个完整的对象。
深拷贝应用实例
- 使用 重写 clone 方法实现深拷贝
- 使用序列化来实现深拷贝
浅拷贝的介绍
- 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
- 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值
- 前面我们克隆羊就是浅拷贝
- 浅拷贝是使用默认的 clone()方法来实现
sheep = (Sheep) super.clone();
深拷贝基本介绍
- 复制对象的所有基本数据类型的成员变量值
- 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝
-
深拷贝实现方式 1:重写 clone 方法来实现深拷贝
多重浅拷贝,就变为了深拷贝。将引用类型全部浅拷贝完,只剩下基本数据类型即可。
-
深拷贝实现方式 2:通过对象序列化实现深拷贝(推荐)
序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
可以联想到 xml 序列化,用 xml 来存储或传输对象
因为对象是复合引用类型的所以不好持久存储或传输,只能序列化,化为 xml 即可表示对象,继而传输序列化深拷贝是将对象输出流读入对象,再利用字节数组输出流进行输出,对象输入流又利用字节数组输入流进行读入,封装成一个新的对象。在输出时,序列化会将对象的所有相关信息,包括其拥有的引用类型的对象一起打包输出(而不是只输出引用类型的地址) ,所以接收的时候我们可以得到一个完整的对象。
深拷贝应用实例
- 使用 重写 clone 方法实现深拷贝
- 使用序列化来实现深拷贝
代码演示
package com.nemo.prototype.deepclone;
import java.io.Serializable;
public class DeepCloneableTarget implements Serializable,Cloneable {
private static final long serialVersionUID = 1L;
private String cloneName;
private String cloneClass;
//构造器
public DeepCloneableTarget(String cloneName,String cloneClass) {
this.cloneName = cloneName;
this.cloneClass = cloneClass;
}
//因为该类的属性,都是 String,因此我们这里使用默认的 clone 完成即可
//super.clone() 调用的是父类的克隆方法,默认Object父类,因为Java只能单继承,所以父类super对象唯一。
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
//...
package com.nemo.prototype.deepclone;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class DeepProtoType implements Serializable,Cloneable{
public String name; //String 属 性
public DeepCloneableTarget deepCloneableTarget;// 引用类型
public DeepProtoType() {
super();
}
//深拷贝 - 方式 1 使用 clone 方法
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub return deepProtoType;
Object deep = null;
//这里完成对基本数据类型(属性)和 String 的克隆
deep = super.clone();
//对引用类型的属性,进行单独处理
DeepProtoType deepProtoType = (DeepProtoType)deep;
deepProtoType.deepCloneableTarget = (DeepCloneableTarget)deepCloneableTarget.clone();
}
//深拷贝 - 方式 2 通过对象的序列化实现 (推荐)
public Object deepClone() {
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(bos);
oos.writeObject(this); //当前这个对象以对象流的方式输出
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray()); ois = new ObjectInputStream(bis);
DeepProtoType copyObj = (DeepProtoType)ois.readObject();
return copyObj;
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
return null;
} finally {
//关闭流
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e2) {
// TODO: handle exception
System.out.println(e2.getMessage());
}
}
}
}
//Client.java
package com.nemo.prototype.deepclone;
public class Client {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
DeepProtoType p = new DeepProtoType();
p.name = "宋江";
p.deepCloneableTarget = new DeepCloneableTarget("大牛","小牛");
//方式 1 完成深拷贝
// DeepProtoType p2 = (DeepProtoType) p.clone();
//
// System.out.println("p.name=" + p.name + "p.deepCloneableTarget=" + p.deepCloneableTarget.hashCode());
// System.out.println("p2.name=" + p.name + "p2.deepCloneableTarget=" + p2.deepCloneableTarget.hashCode());
//方式 2 完成深拷贝
DeepProtoType p2 = (DeepProtoType) p.deepClone();
System.out.println("p.name=" + p.name + "p.deepCloneableTarget=" + p.deepCloneableTarget.hashCode());
System.out.println("p2.name=" + p.name + "p2.deepCloneableTarget=" + p2.deepCloneableTarget.hashCode());
}
}