一、原型模式简介
1、原型模式概述
(1)介绍
- 原型模式(Prototype Pattern)是指用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象,即用于创建重复的对象。
- 原型模式属于创建型模式,它提供了一种创建对象的最佳方式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
- 这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
(2)何时使用
- 当一个系统应该独立于它的产品创建,构成和表示时。
- 当要实例化的类是在运行时刻指定时,例如,通过动态装载。
- 为了避免创建一个与产品类层次平行的工厂类层次时。
- 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
(3)使用场景
- 资源优化场景。
- 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
- 性能和安全要求的场景。
- 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
- 一个对象多个修改者的场景。
- 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
- 在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。
(4)注意事项
- 与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。
二、原型模式示例
假如有一个人Person,姓名张三,年龄20,性别男,现在需要编写程序创建5个完全以往的人。下面用传统方式和原型模式进行分析
1、传统模式
(1)传统模式示例
创建一个Person类,其具有属性Name,Age,Sex
public class Person {
/*属性*/
private String Name;
private int Age;
private String Sex;
/*构造器*/
public Person(String name, int age, String sex) {
Name = name;
Age = age;
Sex = sex;
}
/*getter/setter方法,省略*/
/*重写toString()*/
@Override
public String toString() {
return "Person{" +
"Name='" + Name + '\'' +
", Age=" + Age +
", Sex='" + Sex + '\'' +
'}';
}
}
main方法类创建Person的对象
/*传统模式*/
public class Yxms1 {
public static void main(String[] args) {
/*创建第一个人*/
Person person1 = new Person("张三", 20, "男");
/*创建剩下九个人时,从第一个人的属性中直接获取属性*/
Person person2 = new Person(person1.getName(), person1.getAge(), person1.getSex());
Person person3 = new Person(person1.getName(), person1.getAge(), person1.getSex());
Person person4 = new Person(person1.getName(), person1.getAge(), person1.getSex());
Person person5 = new Person(person1.getName(), person1.getAge(), person1.getSex());
System.out.println("person1 = " + person1);
System.out.println("person2 = " + person2);
System.out.println("person3 = " + person3);
System.out.println("person4 = " + person4);
System.out.println("person5 = " + person5);
}
}
输出结果为
person1 = Person{Name='张三', Age=20, Sex='男'}
person2 = Person{Name='张三', Age=20, Sex='男'}
person3 = Person{Name='张三', Age=20, Sex='男'}
person4 = Person{Name='张三', Age=20, Sex='男'}
person5 = Person{Name='张三', Age=20, Sex='男'}
(2)传统模式分析
- 传统模式优点是比较好理解,简单易操作;
- 但是传统模式在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低;
- 传统模式总是需要重新初始化对象,而不是动态地获得对象运行时的状态,不够灵活。
2、原型模式
(1)原型模式示例
鉴于传统模式的缺点和不足,可以用原型模式加以改进。我们知道Java中Object类 是所有类的根类,Object类提供 了一个clone()方法,该方法可以将一个Java对象复制一份,但是需要实现clone的Java类必须要实现一个接口Cloneable,该接口表示该类能够复制且具有复制的能力。
- 实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。
- 原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。
具体示例代码如下
创建Person类,实现Cloneable接口,并重写clone()方法。
/*实现Cloneable接口*/
public class Person implements Cloneable{
/*属性*/
private String Name;
private int Age;
private String Sex;
/*构造器*/
public Person(String name, int age, String sex) {
Name = name;
Age = age;
Sex = sex;
}
/*getter/setter方法,省略*/
/*重写toString()*/
@Override
public String toString() {
return "Person{" +
"Name='" + Name + '\'' +
", Age=" + Age +
", Sex='" + Sex + '\'' +
'}';
}
/*重写clone()方法*/
@Override
protected Object clone(){
Person person = null;
try {
person = (Person) super.clone();
}catch (Exception e){
e.printStackTrace();
}
return person;
}
}
main()方法类创建示例
public class Yxms2 {
public static void main(String[] args) {
/*创建第一个人*/
Person person1 = new Person("张三", 20, "男");
/*使用原型模式创建(克隆)剩下的人*/
Person person2 = (Person) person1.clone();
Person person3 = (Person) person1.clone();
Person person4 = (Person) person1.clone();
Person person5 = (Person) person1.clone();
System.out.println("person1 = " + person1);
System.out.println("person2 = " + person2);
System.out.println("person3 = " + person3);
System.out.println("person4 = " + person4);
System.out.println("person5 = " + person5);
}
}
输出结果为
person1 = Person{Name='张三', Age=20, Sex='男'}
person2 = Person{Name='张三', Age=20, Sex='男'}
person3 = Person{Name='张三', Age=20, Sex='男'}
person4 = Person{Name='张三', Age=20, Sex='男'}
person5 = Person{Name='张三', Age=20, Sex='男'}
(2)原型模式分析
- 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率;
- 原型模式不用重新初始化对象,而是动态地获得对象运行时的状态;
- 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的编号,无需修改代码;
- 原型模式在实现深克隆的时候可能需要比较复杂的代码;
- 缺点是原型模式需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则。
三、原型模式中的浅拷贝和深拷贝
1、浅拷贝
(1)浅拷贝介绍
浅拷贝是指将对象中的所有字段复制到新的对象中。其中,值类型字段被复制到新对象中后,在新对象中的修改不会影响到原先对象的值。而新对象的引用类型则是原先对象引用类型的引用,不是引用自己对象本身。在新对象中修改引用类型的值会影响到原先对象,理论上String也是引用类型,但是由于由于该类型比较特殊,Object.MemberwiseClone()方法依旧为其新对象开辟了新的内存空间存储String的值,在浅拷贝中把String类型当作’值类型’即可。
- 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象;
- 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值;
- 浅拷贝是使用默认的clone()方法来实现sheep = (Sheep) super.clone()。
2、深拷贝
(1)深拷贝介绍
深拷贝是指同样也是拷贝,但是与浅拷贝不同的是,深拷贝会对引用类型重新在创新一次(包括值类型),在新对象做的任何修改都不会影响到源对象本身。
- 复制对象的所有基本数据类型的成员变量值
- 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝
3、浅拷贝/深拷贝示例
(1)深拷贝第一种方式
创建一个类实现Serializable和Cloneable接口,并重写clone方法
public class DeepClone implements Serializable,Cloneable {
private static final long serialVersionUID = 1L;
private String Name;
private String Age;
/*构造器*/
public DeepClone(String Name, String Age) {
this.Name = Name;
this.Age = Age;
}
/*重写clone方法*/
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
创建另一个类也实现Serializable和Cloneable接口,并且重写clone方法,这个类中含有DeepClone 类的引用
public class DeepCloneType implements Serializable,Cloneable {
public String Name;
public DeepClone deepClone; //引用类型的属性
public DeepCloneType() {
super();
}
/*重写clone方法*/
@Override
protected Object clone() throws CloneNotSupportedException {
Object deep = null;
/*基本数据类型的克隆*/
deep = super.clone();
/*引用类型的克隆*/
DeepCloneType deepCloneType = (DeepCloneType) deep;
deepCloneType.deepClone = (DeepClone) deepClone.clone();
return deepCloneType;
}
}
main方法类调用拷贝
public class Skb {
public static void main(String[] args) throws Exception {
DeepCloneType dType = new DeepCloneType();
dType.Name = "张三";
dType.deepClone = new DeepClone("李四","");
/*深拷贝*/
DeepCloneType dType1 = (DeepCloneType) dType.clone();
System.out.println("dType.Name = " + dType.Name + " dType.deepClone = " + dType.deepClone.hashCode());
System.out.println("dType1.Name = " + dType1.Name + " dType1.deepClone = " + dType1.deepClone.hashCode());
}
}
输出结果为
dType.Name = 张三 dType.deepClone = 22307196
dType1.Name = 张三 dType1.deepClone = 10568834
结果显示,引用类型的属性的hashCode时不一样的,说明是经过深拷贝的。
(2)深拷贝第二种方式
同样的,创建一个类实现Serializable和Cloneable接口,并重写clone方法
public class DeepClone implements Serializable,Cloneable {
private static final long serialVersionUID = 1L;
private String Name;
private String Target;
/*构造器*/
public DeepClone(String Name, String Target) {
this.Name = Name;
this.Target = Target;
}
/*重写clone方法*/
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
创建另一个类也实现Serializable和Cloneable接口,这个类中含有DeepClone 类的引用,在这个类中,以序列化的方式实现深拷贝
public class DeepCloneType implements Serializable,Cloneable {
public String Name;
public DeepClone deepClone; //引用类型的属性
public DeepCloneType() {
super();
}
/*通过对象的序列化实现深拷贝*/
public Object deepClone(){
/*创建输入输出流*/
ByteArrayOutputStream baos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
try {
/*序列化*/
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(this);
/*反序列化*/
bais = new ByteArrayInputStream(baos.toByteArray());
ois = new ObjectInputStream(bais);
DeepCloneType deepCloneType = (DeepCloneType) ois.readObject();
return deepCloneType;
}catch (Exception e){
e.printStackTrace();
return null;
}finally {
if(baos != null){
try {
baos.close();
} catch (Exception e1) {
e1.printStackTrace();
}
}
if(oos != null){
try {
oos.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
if(bais != null){
try {
bais.close();
} catch (Exception e3) {
e3.printStackTrace();
}
}
if(ois != null){
try {
ois.close();
} catch (Exception e4) {
e4.printStackTrace();
}
}
}
}
}
main方法类调用拷贝
public class Skb {
public static void main(String[] args) throws Exception {
DeepCloneType dType = new DeepCloneType();
dType.Name = "张三";
dType.deepClone = new DeepClone("李四","");
/*深拷贝*/
DeepCloneType dType1 = (DeepCloneType) dType.deepClone();
System.out.println("dType.Name = " + dType.Name + " dType.deepClone = " + dType.deepClone.hashCode());
System.out.println("dType1.Name = " + dType1.Name + " dType1.deepClone = " + dType1.deepClone.hashCode());
}
}
输出结果为
dType.Name = 张三 dType.deepClone = 11110316
dType1.Name = 张三 dType1.deepClone = 17061334
结果显示,以这种方式实现的深拷贝,引用类型的属性的hashCode也不一样的,说明是经过深拷贝的。