原型模式
原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象,用大白话来说明的话,就是类似西游记中孙大圣拔出猴毛, 用猴毛变出其它跟自己一模一样的孙大圣。
问题由来:
类比孙大圣通过猴毛变出多个分身的效果,假设孙大圣姓名为: 孙悟空,别称:齐天大圣, 年龄为:8888岁,性别:男,然后我们通过编写程序来复制出属性跟孙大圣完全相同的另一只猴子。
首先我们先定义一个猴子类
package prototype.normal;
import java.util.Map;
public class Monkey {
private Map<String,String> name;
private Integer age;
private Character gender;
public Monkey(Map<String,String> name, Integer age, Character gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public Map<String,String> getName() {
return name;
}
public void setName(Map<String,String> name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Character getGender() {
return gender;
}
public void setGender(Character gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Monkey{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
'}';
}
}
之后我们模拟孙大圣变分身的效果
package prototype.normal;
import java.util.HashMap;
import java.util.Map;
public class TestClone {
public static void main(String[] args) {
Map<String,String> name = new HashMap<>();
name.put("名字","孙悟空");
name.put("别称","齐天大圣");
Monkey monkey1 = new Monkey(name,8888,'男');
Monkey monkey2 = new Monkey(monkey1.getName(),monkey1.getAge(),monkey1.getGender());
System.out.println("monkey1:" + monkey1);
System.out.println("monkey2:" + monkey2);
}
}
虽然这种方式好理解,简单易操作。但是每次在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,总是需要重新初始化对象,效率低下。下面我们通过原型模式来优化设计。
原型模式
和上面一样,定义一个猴子类,不过区别的话,就是要实现cloneable接口,这个只是起到一个标志,说明该类可以clone,如果没实现的话,就会报CloneNotSupportedException,然后通过重写Object类的clone方法来实现clone。
package prototype.shallowclone;
import java.util.Map;
public class Monkey implements Cloneable{
private Map<String,String> name;
private Integer age;
private Character gender;
public Monkey(Map<String,String> name, Integer age, Character gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public Map<String,String> getName() {
return name;
}
public void setName(Map<String,String> name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Character getGender() {
return gender;
}
public void setGender(Character gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Monkey{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
'}';
}
//克隆该实例,使用默认的clone方法来完成
@Override
protected Object clone() {
Monkey monkey = null;
try {
monkey = (Monkey) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return monkey;
}
}
之后我们模拟孙大圣变分身的效果
package prototype.shallowclone;
import java.util.HashMap;
import java.util.Map;
public class TestClone {
public static void main(String[] args) {
Map<String,String> name = new HashMap<>();
name.put("名字","孙悟空");
name.put("别称","齐天大圣");
Monkey monkey1 = new Monkey(name,8888,'男');
Monkey monkey2 = (Monkey) monkey1.clone();
System.out.println("monkey1:" + monkey1);
System.out.println("monkey2:" + monkey2);
}
}
原型模式可以很方面的实现对象的拷贝,但是当我们去修改拷贝对象的属性,却有可能发生一些问题,下面看下这个例子。
和上面一样,定义一个猴子类,不过区别的话,就是要实现cloneable接口,这个只是起到一个标志,说明该类可以clone,如果没实现的话,就会报CloneNotSupportedException,然后通过重写Object类的clone方法来实现clone。
package prototype.shallowclone;
import java.util.Map;
public class Monkey implements Cloneable{
private Map<String,String> name;
private Integer age;
private Character gender;
public Monkey(Map<String,String> name, Integer age, Character gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public Map<String,String> getName() {
return name;
}
public void setName(Map<String,String> name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Character getGender() {
return gender;
}
public void setGender(Character gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Monkey{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
'}';
}
//克隆该实例,使用默认的clone方法来完成
@Override
protected Object clone() {
Monkey monkey = null;
try {
monkey = (Monkey) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return monkey;
}
}
之后我们模拟孙大圣变分身的效果,然后我们把拷贝的孙大圣的名字和别称给改了,你猜猜会发生什么问题呢?
package prototype.shallowclone;
import java.util.HashMap;
import java.util.Map;
public class TestClone {
public static void main(String[] args) {
Map<String,String> name = new HashMap<>();
name.put("名字","孙悟空");
name.put("别称","齐天大圣");
Monkey monkey1 = new Monkey(name,8888,'男');
Monkey monkey2 = (Monkey) monkey1.clone();
System.out.println("修改克隆对象属性前");
System.out.println("monkey1:" + monkey1);
System.out.println("monkey2:" + monkey2);
monkey2.getName().put("名字","猪八戒");
monkey2.getName().put("别称","净坛使者");
System.out.println("修改克隆对象属性后");
System.out.println("monkey1:" + monkey1);
System.out.println("monkey2:" + monkey2);
}
}
运行结果
修改克隆对象属性前
monkey1:Monkey{name='{名字=孙悟空, 别称=齐天大圣}', age=8888, gender=男}
monkey2:Monkey{name='{名字=孙悟空, 别称=齐天大圣}', age=8888, gender=男}
修改克隆对象属性后
monkey1:Monkey{name='{名字=猪八戒, 别称=净坛使者}', age=8888, gender=男}
monkey2:Monkey{name='{名字=猪八戒, 别称=净坛使者}', age=8888, gender=男}
很奇妙,从运行结果来看,对拷贝的对象进行修改,居然会对原对象也会造成影响,这就要引入浅拷贝和深拷贝的概念了。
浅拷贝
1)对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象;
2)对于数据类型是引用数据类型的成员变量(string类型虽然也是引用数据类型,但是他因为是用final修饰的,常量不可修改,即使对克隆出来的新对象进行修改,实际上是改变了克隆出来对象String类型成员的指向,不会影响被克隆对象的值及其指向),比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
上面的克隆孙大圣的例子就是浅拷贝。
深拷贝
1)复制对象的所有基本数据类型的成员变量值;
2) 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝。
深拷贝有两种实现方法:
1、重写clone方法来实现深拷贝
2、通过对象序列化实现深拷贝(推荐)
因为通过clone方法实现深拷贝有点复杂,加上不推荐使用,所以我只讲下通过对象序列化实现深拷贝的方式。
和上面一样,定义一个猴子类,不过区别的话,就是定义对象序列化的方法。
package prototype.deepclone;
import java.io.*;
import java.util.Map;
public class Monkey implements Serializable {
private Map<String, String> name;
private Integer age;
private Character gender;
public Monkey(Map<String, String> name, Integer age, Character gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public Map<String, String> getName() {
return name;
}
public void setName(Map<String, String> name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Character getGender() {
return gender;
}
public void setGender(Character gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Monkey{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
'}';
}
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);
Monkey copyObj = (Monkey) 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) {
System.out.println(e2.getMessage());
}
}
}
}
之后实现孙大圣变出分身的效果。
package prototype.deepclone;
import java.util.HashMap;
import java.util.Map;
public class TestClone {
public static void main(String[] args) {
Map<String,String> name = new HashMap<>();
name.put("名字","孙悟空");
name.put("别称","齐天大圣");
Monkey monkey1 = new Monkey(name,8888,'男');
Monkey monkey2 = (Monkey) monkey1.deepClone();
System.out.println("修改克隆对象属性前");
System.out.println("monkey1:" + monkey1);
System.out.println("monkey2:" + monkey2);
monkey2.getName().put("名字","猪八戒");
monkey2.getName().put("别称","净坛使者");
System.out.println("修改克隆对象属性后");
System.out.println("monkey1:" + monkey1);
System.out.println("monkey2:" + monkey2);
}
}
运行结果:
修改克隆对象属性前
monkey1:Monkey{name='{名字=孙悟空, 别称=齐天大圣}', age=8888, gender=男}
monkey2:Monkey{name='{名字=孙悟空, 别称=齐天大圣}', age=8888, gender=男}
修改克隆对象属性后
monkey1:Monkey{name='{名字=孙悟空, 别称=齐天大圣}', age=8888, gender=男}
monkey2:Monkey{name='{名字=猪八戒, 别称=净坛使者}', age=8888, gender=男}
从运行结果可以看出,对克隆出来的新对象的属性进行修改后,不会影响到原被克隆对象的属性。