设计模式之原型模式
背景:如果有一只狗,Jim, 现在需要创建5只,跟Jim一样的狗(属性一样的),按常规的做法如下:
Dog:
public class Dog { private String name; private int age; public Dog(String name, int age) { this.name = name; this.age = age; } 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 void show() { System.out.println("名字:" + name + ",年龄:" + age); } }
Client:
package prototypePattern; public class Client { public static void main(String[] args) { //先把Jim创建出来 Dog jim = new Dog("Jim", 2); //然后创建5只跟Jim一样的狗 Dog dog = new Dog(jim.getName(), jim.getAge()); Dog dog1 = new Dog(jim.getName(), jim.getAge()); Dog dog2 = new Dog(jim.getName(), jim.getAge()); Dog dog3 = new Dog(jim.getName(), jim.getAge()); Dog dog4 = new Dog(jim.getName(), jim.getAge()); } }
分析:
优点:相当的简明,逻辑异常清晰。容易实现。
缺点:总是要去一个个获取Jim的属性,问题是,这仅仅只是个简单例子,万一有一个超级无比大的对象,如果也是这样一个个获取下去,势必会造成效率低下问题,写起来超级累,如果属性个数被拿去拓展,要改超级多的地方。
改进方式:采用原型模式
代码实现如下:
1、Prototype是一个原型类,声明一个克隆自己的接口。在这边相当于Clonable接口。
2、concretePrototype是一个具体的原型类,实现一个克隆自己的操作。
Client:
public class Client { public static void main(String[] args) throws CloneNotSupportedException { //先把Jim创建出来 Dog jim = new Dog("Jim", 2); //然后创建5只跟Jim一样的狗 Dog dog1 = (Dog) jim.clone(); Dog dog2 = (Dog) jim.clone(); Dog dog3 = (Dog) jim.clone(); Dog dog4 = (Dog) jim.clone(); Dog dog5 = (Dog) jim.clone(); } }
Dog:
public class Dog implements Cloneable{ private String name; private int age; public Dog(String name, int age) { this.name = name; this.age = age; } 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; } @Override protected Object clone() throws CloneNotSupportedException { Dog dog = (Dog) super.clone(); return dog; } @Override public String toString() { return "名字:" + name + ",年龄:" + age; } }
优点:让程序有更高的效率和拓展性。这个是很明显,如果你要添加一个属性,用通俗的方式,那每个对象还得都加一遍。
创建出来的对象虽然属性都是一样的,但是是属于不同的对象实例。
深拷贝&浅拷贝
现在问题是,如果每个dog中还有一个引用类型的变量,如数组或者类的对象。
测试如下,在Dog类中添加一个引用的变量。看下结果。
Dog.java(添加一个引用变量)
package prototypePattern; public class Dog implements Cloneable{ private String name; private int age; private Dog friend; public Dog(String name, int age) { this.name = name; this.age = age; } 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 Dog getFriend() { return friend; } public void setFriend(Dog friend) { this.friend = friend; } @Override protected Object clone() throws CloneNotSupportedException { Dog dog = (Dog) super.clone(); return dog; } @Override public String toString() { return "名字:" + name + ",年龄:" + age; } }
Client.java
public class Client { public static void main(String[] args) throws CloneNotSupportedException { //先把Jim创建出来 Dog jim = new Dog("Jim", 2); jim.setFriend(new Dog("Tom", 2)); Dog dog1 = (Dog) jim.clone(); System.out.println("jim:"+jim.hashCode()+" dog1:"+dog1.hashCode()); System.out.println("jim.friend:"+jim.getFriend().hashCode()+" dog1.friend:"+dog1.getFriend().hashCode()); } }
输出结果:
jim:342597804 dog1:1308244637
jim.friend:1860944798 dog1.friend:1860944798
可以看到:虽然jim和dog1是不同的实例,但是里面的引用变量(即friend) 却是同一个实例。所以clone方法实现的属于浅拷贝。
那么如何实现深拷贝?有以下两种方式。
一、重写clone方法的时候对引用类型的变量进行特殊处理
DeepCloneableTarget.java
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; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
import java.io.Serializable; public class DeepProtoType implements Serializable, Cloneable { public String name; public DeepCloneableTarget deepCloneableTarget; public DeepProtoType(String name, DeepCloneableTarget deepCloneableTarget) { this.name = name; this.deepCloneableTarget = deepCloneableTarget; } @Override protected Object clone() throws CloneNotSupportedException { DeepProtoType deepProtoType = (DeepProtoType) super.clone(); deepProtoType.deepCloneableTarget = (DeepCloneableTarget) deepProtoType.deepCloneableTarget.clone(); return deepProtoType; } }
Client.java:
public class Client { public static void main(String[] args) throws CloneNotSupportedException { DeepProtoType deepProtoType = new DeepProtoType("deepProtoType", new DeepCloneableTarget("DeepCloneableTarget-name", "DeepCloneableTarget-class")); DeepProtoType clone = (DeepProtoType) deepProtoType.clone(); System.out.println("deepProtoType:"+deepProtoType.hashCode()+" clone:"+clone.hashCode()); System.out.println("deepProtoType.deepCloneableTarget:"+deepProtoType.deepCloneableTarget.hashCode() +" clone.deepCloneableTarget:"+clone.deepCloneableTarget.hashCode()); } }
输出结果:
deepProtoType:1308244637 clone:1860944798
deepProtoType.deepCloneableTarget:1179381257 clone.deepCloneableTarget:258754732
以上案例:DeepProtoType类中有一个DeepCloneableTarget类型的成员变量,通过clone()中特殊处理后,发现确实实现了深拷贝。但是如果DeepCloneableTarget中还有引用变量的,那么就难办了。还得不断的特殊处理下去。
二、通过序列化、反序列化 (推荐)
@Override protected Object clone() throws CloneNotSupportedException { 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 o = (DeepProtoType) ois.readObject(); return o; } catch (Exception e) { e.printStackTrace(); return null; } finally { try { bos.close(); oos.close(); bis.close(); ois.close(); } catch (IOException e) { System.out.println(e.getMessage()); } } }