原型模式
- 原型模式基本介绍
- 举例和演示
- 深浅拷贝的介绍
- 原型模式的总结
1.原型模式基本介绍
-
原型模式是指: 采用原型实例创建对象的种类,并且通过拷贝这些模型创建新的对象
-
原型模式是一种创建性模式,允许一个对象以自身为模板,创建一个可定制的对象,无需知晓如何创建的细节
-
具体实现: 实现Cloneable接口,重写或者直接使用clone方法
2.举例和演示
假设现在存在一个类,然后得到若干个这个类的复制体,如何实现???
1. 原始方案: 采用在构造函数内赋值的方法
- 代码演示:
```java
package com.liz.GOF23.prototype.origin_way;
public class Sheep {
private String name;
private int age;
private String 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;
}
public Sheep(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
'}';
}
}
public static void main(String[] args) {
Sheep sheep = new Sheep("tom", 1, "white");
//.... copy
//在构造函数内部进行“复制”
Sheep sheepCopy1 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
System.out.println(sheepCopy1);
//.... show
}
谈谈优缺点
- 优点:比较好理解,简单易操作
- 缺点:创建新对象的时候需要重新获取原始对象的属性,如果创建对象比较复杂,效率较低,同时不能动态获取对象的运行状态,不够灵活。
2.改进方案
-
使用原型模式
java的Object类提供了一个clone()方法,该方法可以将一个java对象复制一份,但是需要实现接口Cloneable(该接口表示该类可以复制,并且具有复制的能力),同时调用clone方法 -
代码演示
package com.liz.GOF23.prototype.clone_way;
public class Sheep implements Cloneable {
private String name;
private int age;
private String color;
public Sheep friend;//是对象,克隆会如何处理,默认是浅拷贝
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;
}
public Sheep(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
'}';
}
//克隆该实例
@Override
protected Object clone() throws CloneNotSupportedException {
Sheep sheep = null;//待克隆对象
sheep=(Sheep)super.clone();
return sheep;
}
}
`public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
System.out.println("使用原型模式完成对象的创建");
Sheep sheepModel = new Sheep("tom",1,"白色");
//原始方法
sheepModel.friend = new Sheep("tom_friend",2,"黑色");
//开始克隆(默认是浅拷贝)
Sheep sheep2 = (Sheep) sheepModel.clone();
Sheep sheep3 = (Sheep) sheepModel.clone();
//System.out.println(sheepModel.friend);
//对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组,某个类的对象等。那么浅拷贝会进行引用传递,也就是将该成员变量的引用值(内存地址)复制一份给新的对象(hashcode一致)
System.out.println("sheep2: "+sheep2+" sheep2:friend"+sheep2.friend.hashCode());
System.out.println("sheep3: "+sheep3+" sheep3:friend"+sheep3.friend.hashCode());
}
}
- 运行结果
- 注: 在这里两个“复制体” sheep2,sheep3的引用属性friend的hashcode一致,说明系统提供的clone方法是浅拷贝(引用类型指向同一个引用,hashcode一致)
深浅拷贝的介绍
浅拷贝特性
- 对于数据类型是基本数据类型的成员变量,浅拷贝将该属性值复制一份给新的变量
- 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组,某个类的对象等,那么浅拷贝会进行引用传递,也就是将该成员变量的引用值(内存地址)复制一份给新的对象【即两个引用会指向同一个对象】。
- 多个对象指向堆中的同一个实例,在这种情况下,一个对象去修改该成员变量会影响到另一个对象。
- 代码实现:
上述的原型模式使用的就是浅拷贝,因此两个sheep的friend(引用类型)指向堆中的同一个实例(hashcode一致)
sheep = (Sheep) super.clone();// 浅拷贝是使用默认的clone()实现
深拷贝特性
- 对于数据类型是基本数据类型的成员变量,深拷贝和浅拷贝一致,进行属性值的重新复制
- 对于数据类型是引用数据类型的成员变量,不再进行内存地址的传递,而是在堆中重新开辟一块空间,存放该对象实例的复制,然后被“复制体”所引用
- 如何实现深拷贝
-
重写clone方法(即对出现存在引用类型,单独对引用类型进行处理)【类似剥洋葱,给引用类型加上clone方法,直到该类没有引用类型为止】
-
采用序列化和反序列化的方式进行深拷贝
- 代码演示:
-
//引用实例
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完成即可
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
//待复制的实例
//Serializable 序列化(将对象的信息转化为可以存储或者传输的过程)
public class DeepProtoType implements Serializable, Cloneable {
public String name;//String 属性
public DeepCloneableTarget deepCloneableTarget;//引用类型(自定义clone需要改造这个引用类)
public DeepProtoType() {
super();
}
//浅拷贝
// @Override
// protected Object clone() throws CloneNotSupportedException {
// return super.clone();
// }
//深拷贝实现1 重写clone
//对引用类型一层一层进行向下复制 直到出现没有引用类型即可(此时需要去给DeepCloneableTarget 加上clone方法)
@Override
protected Object clone() throws CloneNotSupportedException {
//完成对基本数据类型(属性)和String的克隆
Object deep = null;
deep = super.clone();
//对引用类型的属性进行单独处理
DeepProtoType deepProtoType = (DeepProtoType) deep;
//!!! 引用类型的clone(该引用类型内部全部为String 无其他引用实例)
deepProtoType.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone(); //!!!! 由于这里实现了clone方法
return deepProtoType;
}
//-------------------------------------------------------------------------------------
//深拷贝实现2 通过对象的序列化和反序列化实现深拷贝(!! 推荐使用)
public Object deepClone() throws IOException {
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
//开始序列化操作
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);//将当前对象以对象流的形式进行输出(序列化)
//开始反序列化操作
//bis读出了bos的信息 相当于克隆
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
DeepProtoType cloneDemo = (DeepProtoType) ois.readObject();
return cloneDemo;
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
finally {
bos.close();
oos.close();
bis.close();
ois.close();
}
return null;
}
//结果演示
public class Client {
public static void main(String[] args) throws CloneNotSupportedException, IOException {
DeepProtoType dpt = new DeepProtoType();
dpt.name = "1z";
dpt.deepCloneableTarget = new DeepCloneableTarget("box","Box");
/**
* 自定义: 深拷贝
* 默认: 浅拷贝
* */
//方案1 重写clone
//DeepProtoType dptClone = (DeepProtoType) dpt.clone();
//方案2 对象的序列化和反序列化
DeepProtoType dptClone = (DeepProtoType)dpt.deepClone();
//比较引用类型的hashcode
//!!! 在这里hashcode不一样 表示深拷贝成功
System.out.println(dpt.deepCloneableTarget.hashCode());
System.out.println(dptClone.deepCloneableTarget.hashCode());
}
}
-
深拷贝代码运行结果:
hashcode的值不同,表示深拷贝实现了引用类型不再进行内存地址的传递,而是在堆中重新开辟一块空间,存放该对象实例的复制,然后被“复制体”所引用
!!! 推荐使用序列化和反序列化的方式,进行深拷贝
原型模式的总结
- 当创建对象比较复杂时,可以利用原型模式简化对象的创建过程,同时提高效率
- 原型模式可以动态获得对象的状态,如果修改原型对象的属性,复制对象自动进行修改属性
- 原型对象同样存在缺点: 如果对已有的类进行原型模式修改的时候,需要修改源代码,违反了OCP(开放封闭原则),所以在设计时就要考虑是否该实例需要复制。