用原型对象创建对象的种类,并通过拷贝这些原型创建新的对象。在原型设计模式中,这些新建的对象会非常多,它们最大的区别在于初始化时会有所不同,而且还可以被分为若干类。例如在界面设计器中,你可以任意的拖拽出button控件,但是你的界面中可能会需要很多button,你可以选择每次都new一个button给用户,但是更好的办法是clone一个原始button,然后用户自定义这个button属性,这样如果用户复制button时也可以用这个clone方法。
原型模式能够对客户隐藏具体的产品类,从而减少客户需要知道的类数目。原型模式可以和抽象工厂结合,在创建工厂时定制一个原型,然后就可以在任意时刻使用这个定制好后的原型创建新的产品出来。另外,原型模式还会有深拷贝(deep copy)和浅拷贝(shallow copy)之分,深拷贝会复制对象的属性出来,浅拷贝只会复制对象属性的引用。
1.自定义原型
public interface Prototype {
public String getName();
public void setName(String name);
public Prototype myclone();
}
public class ConcretePrototype1 implements Prototype {
private String name = null;
public ConcretePrototype1(String name) {
this.name = name;
}
@Override
public String getName() {
return this.name;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public Prototype myclone() {
return new ConcretePrototype1(new String(name));// deep copy
}
}
public class ConcretePrototype2 implements Prototype {
private String name = null;
public ConcretePrototype2(String name) {
this.name = name;
}
@Override
public String getName() {
return this.name;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public Prototype myclone() {
return new ConcretePrototype2(name);// shallow copy
}
}
public class Client {
@Test
public void operation() {
// deep copy
Prototype p1 = new ConcretePrototype1("abc");
Prototype q1 = p1.myclone();
assertFalse(p1.getName() == q1.getName());
// shallow copy
Prototype p2 = new ConcretePrototype2("bcd");
Prototype q2 = p2.myclone();
assertTrue(p2.getName() == q2.getName());
}
}
定义Prototype接口,所有需要使用clone方法的对象都实现这个接口,并实现其中的myclone方法。其中ConcretePrototype1实现了深拷贝,也就意味着拷贝时连带对象的属性一起进行了拷贝,而ConcretePrototype2实现了浅拷贝,只是把新对象的属性值指向了原对象相同的那么对象的属性地址。浅拷贝时,如果对象属性是一个可变对象的话,修改原对象和新对象都会影响对方的这个属性的值。
2.Java中支持的原型(使用Object的clone方法,需要继承Cloneable接口)
public class Address implements Cloneable{
private String city;
public Address(String city) {
this.city = city;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public Object clone(){
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
public class CloneablePrototype1 implements Cloneable {
private String name = null;
private Address addr = null;
public CloneablePrototype1(String name, Address addr) {
this.name = name;
this.addr = addr;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
//shallow copy
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
public class CloneablePrototype2 implements Cloneable {
private String name = null;
private Address addr = null;
public CloneablePrototype2(String name, Address addr) {
this.name = name;
this.addr = addr;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
// deep copy
public Object clone() {
try {
CloneablePrototype2 pro = (CloneablePrototype2) super.clone();
pro.setName(new String(name));
pro.setAddr((Address) addr.clone());
return pro;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
public class CloneableClient {
@Test
public void shallowcopy() {
Address addr = new Address("Wuhan");
CloneablePrototype1 p1 = new CloneablePrototype1("abc", addr);
CloneablePrototype1 q1 = (CloneablePrototype1) p1.clone();
assertTrue(p1.getAddr() == q1.getAddr());//shallow copy
assertTrue(p1.getName() == q1.getName());
CloneablePrototype2 p2 = new CloneablePrototype2("bcd", addr);
CloneablePrototype2 q2 = (CloneablePrototype2) p2.clone();
assertFalse(p2.getAddr() == q2.getAddr());//deep copy
assertFalse(p2.getName() == q2.getName());
}
}
使用Java中的Cloneable来实现clone,在Object对象中包含了protected的clone方法,这样只要实现了Cloneable接口,并覆盖Object的clone方法就可以实现对象的clone。这里也区分深拷贝和浅拷贝,浅拷贝可以直接通过super.clone来实现,但是深拷贝需要创建拷贝对象后再去设定新对象的属性实现深拷贝,这就意味着是否进行深拷贝可以由你自由掌握。
3.使用java中的串行化实现深拷贝
这个方法的确很巧妙,利用Object的IO Stream来读取和解析对象,这样就可以生成和原来对象相同的对象了。但是这个方法要求被克隆的自定义对象的所有域都实现Serializable接口,否则会出现java.io.NotSerializableException错误。
/**
* reference:
* <a href='http://www.cnblogs.com/itTeacher/archive/2012/12/02/2797857.html'>
* Java设计模式四: 原型模式(Prototype Pattern)</a>
*/
public class SerializablePrototype implements Serializable {
private static final long serialVersionUID = 4653567423161077789L;
private SerializableName name = null;
public SerializablePrototype(SerializableName name) {
this.setName(name);
}
public SerializableName getName() {
return name;
}
public void setName(SerializableName name) {
this.name = name;
}
// deep copy
public Object deepClone() {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(out);
objOut.writeObject(this);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream objIn = new ObjectInputStream(in);
return objIn.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static class SerializableName implements Serializable {
private static final long serialVersionUID = 2407657721744992188L;
private String name;
public SerializableName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return name;
}
}
}
public class SerializableClient {
@Test
public void test() {
SerializableName name = new SerializableName("Wuhan");
SerializablePrototype p = new SerializablePrototype(name);
SerializablePrototype q = (SerializablePrototype) p.deepClone();
System.out.println(q.getName().toString());
assertFalse(p.getName() == q.getName());
}
}
这个方法相对有限制,要求属性也必须实现Serializable接口,如果不实现就会出现Exception。实现clone时,将当前对象通过ObjectOutputStream转化为二进制流输入ByteArrayOutputStream,之后将这个output stream交给ByteArrayInputStream,然后就可以通过ObjectInputStream读取这个input stream重构这个对象。