原型模式是用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。
类图:
在这里prototype是一个原型类,声明一个克隆自己的接口。
Concrete Prototype是具体的原型类,实现一个克隆自己的操作。
Client是让一个一个原型对象克隆自己,从而创建一个新的对象。
public class Dog implements Cloneable {
private String name;
private Integer age;
public Dog(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
Dog dog = null;
try {
dog = (Dog) super.clone();
}catch (Exception e){
e.printStackTrace();
}
return dog;
}
}
public class Client {
public static void main(String[] args) throws Exception {
Dog dog = new Dog("Tom",10);
//Dog dog2 = new Dog(dog.getName(),dog.getAge());
//Dog dog3 = new Dog(dog.getName(),dog.getAge());
//Dog dog4 = new Dog(dog.getName(),dog.getAge());
//上面是传统的写法
Dog dog1 = (Dog) dog.clone();
Dog dog5 = (Dog) dog.clone();
Dog dog6 = (Dog) dog.clone();
Dog dog7 = (Dog) dog.clone();
System.out.println(dog1);
System.out.println(dog5);
System.out.println(dog6);
System.out.println(dog7); //结果都是一样的狗
}
}
上面代码用传统写法跟原型模式比较,传统的写法每次创建一个狗对象都需要明确的知道它的名字与年龄,然后通过构造方法往里面set,如果狗的属性有很多的话这样操作会比较繁琐,而下面那种只需要给第一只狗加入名字与年龄,之后的克隆可以不需要知道狗的各种细节,可直接克隆一只一模一样的狗(对象)。
上面的拷贝可以称为浅拷贝,对于数据类型是引用数据类型的成员变量,比如是一个数组、对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。演示一下:
// 加一个铃铛类来测试
public class LinDang implements Cloneable{
private String shape;
private String color;
public LinDang(String shape, String color) {
this.shape = shape;
this.color = color;
}
public String getShape() {
return shape;
}
public void setShape(String shape) {
this.shape = shape;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "LinDang{" +
"shape='" + shape + '\'' +
", color='" + color + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public static void main(String[] args) throws Exception {
Dog dog = new Dog("Tom",10);
LinDang linDang = new LinDang("圆形","红色");//创建一个铃铛
dog.setLinDang(linDang);
Dog dog1 = (Dog) dog.clone();
System.out.println(dog);
System.out.println(dog1);
dog.getLinDang().setColor("黑色");
System.out.println("只修改dog狗铃铛为黑色");
System.out.println(dog);
System.out.println(dog1);
/**
* 输出结果:
* Dog{name='Tom', age=10, linDang=LinDang{shape='圆形', color='红色'}}
* Dog{name='Tom', age=10, linDang=LinDang{shape='圆形', color='红色'}}
* 只修改dog狗铃铛为黑色
* Dog{name='Tom', age=10, linDang=LinDang{shape='圆形', color='黑色'}}
* Dog{name='Tom', age=10, linDang=LinDang{shape='圆形', color='黑色'}}
*/
}
本意是改dog的铃铛,结果dog1的铃铛也改了。而使用深拷贝可以解决这个问题。实现深拷贝有两种方式:一种是通过克隆来实现,一种是通过序列化来实现。
1.通过克隆来实现:稍微改一下上面Dog类的clone()方法就好了
@Override
protected Object clone() throws CloneNotSupportedException {
Dog dog = null;
try {
dog = (Dog) super.clone();
dog.setLinDang((LinDang) dog.getLinDang().clone());
//相当于手动再把LingDang对象也克隆一遍
}catch (Exception e){
e.printStackTrace();
}
return dog;
}
2.通过序列化来实现:Dog类和LingDang类实现Serializable接口,在Dog类里添加深度克隆的方法。
public class Dog implements Serializable {
private String name;
private Integer age;
private LinDang linDang;
public Dog(String name, Integer age) {
this.name = name;
this.age = age;
}
public Dog(String name, Integer age, LinDang linDang) {
this.name = name;
this.age = age;
this.linDang = linDang;
}
public LinDang getLinDang() {
return linDang;
}
public void setLinDang(LinDang linDang) {
this.linDang = linDang;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
", linDang=" + linDang +
'}';
}
public Object deepClone()throws Exception{
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);
return ois.readObject();
}catch (Exception e){
e.printStackTrace();
return null;
}finally {
//关闭流
try {
bos.close();
oos.close();
bis.close();
ois.close();
}catch (Exception e2){
e2.printStackTrace();
}
}
}
}
在mian方法测试,结果与第一种一致。原型模式需要为每一个类配备一个克隆方法,对已有的类进行改造时,需要修改其源代码,在这里就违背了ocp原则(对扩展开放,对修改关闭)。