原型模式是一种创建型设计模式,通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象。


Java设计模式—原型模式(prototype pattern)_java



原型模式要求对象实现一个可以“克隆”自身的接口,该接口通过复制一个实例对象本身来创建一个新的实例。那么原型实例创建新的对象时就无需关心这个实例本身的类型,只要实现克隆自身的方法,就可以通过这个方法来获取新的对象,没必要再去通过new来创建。


原型模式的两种表现形式

原型模式有两种表现形式(仅仅是原型模式的不同实现):简单形式和登记形式。


  简单形式的原型模式


Java设计模式—原型模式(prototype pattern)_java_02


这种模式涉及到三种角色分别如下:


1)客户(Client)角色:客户类提出创建对象的请求。

public class Client {    /**     * 原型接口对象     */    private Prototype prototype;    /**     * 构造方法     */    public Client(Prototype prototype){        this.prototype = prototype;    }    public void operation(Prototype example){        Prototype copyPrototype = prototype.clone();
   }}


2)抽象原型(Prototype)角色:这是一个抽象角色,通常由一个Java接口或Java抽象类实现。此角色给出所有的具体原型类所需的接口。

public interface Prototype{    /**     * 克隆自身的方法     */    public Object clone();}


3)具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象的原型角色所要求的接口。

public class ConcretePrototype1 implements Prototype {    public Prototype clone(){        Prototype prototype = new ConcretePrototype1();        return prototype;    }}
public class ConcretePrototype2 implements Prototype {    public Prototype clone(){        //最简单的克隆,新建一个自身对象,由于没有属性就不再复制值了        Prototype prototype = new ConcretePrototype2();        return prototype;    }}


  登记形式的原型模式


登记形式相比简单形式多了一个原型管理器(PrototypeManager)角色,该角色的作用是创建具体原型类的对象,并记录每一个被创建的对象。



原型管理器角色保持一个聚集,作为对所有原型对象的登记,这个角色提供必要的方法,供外界增加新的原型对象和取得已经登记过的原型对象。

public class PrototypeManager {    /**     * 记录原型的编号和原型实例的对应关系     */    private static Map<String,Prototype> map = new HashMap<String,Prototype>();    /**     * 私有化无参构造方法     */    private PrototypeManager(){}    /**     * 原型管理器添加或是修改某个原型注册     * @param prototypeId 原型编号     * @param prototype    原型实例     */    public synchronized static void setPrototype(String prototypeId , Prototype prototype){        map.put(prototypeId, prototype);    }    /**     * 从原型管理器中删除某个原型注册     * @param prototypeId 原型编号     */    public synchronized static void removePrototype(String prototypeId){        map.remove(prototypeId);    }    /**     * 获取某个原型编号对应的原型实例     * @param prototypeId    原型编号     * @return    原型编号对应的原型实例     * @throws Exception    如果原型编号对应的实例不存在,则抛出异常     */    public synchronized static Prototype getPrototype(String prototypeId) throws Exception{        Prototype prototype = map.get(prototypeId);        if(prototype == null){            throw new Exception("您希望获取的原型还没有注册或已被销毁");        }        return prototype;    }}


  两种形式的区别


当创建的原型对象数目较少而且比较固定的话,采取简单形式。在这种情况下,原型对象的引用可以由客户端自己保存。


当创建的原型对象数目不固定的话,采取登记形式。在这种情况下,客户端不保存对原型对象的引用,这个任务被交给管理员对象。在复制一个原型对象之前,客户端可以查看管理员对象是否已经有一个满足要求的原型对象。如果有,可以直接从管理员类取得这个对象引用;反之,客户端就需要自行复制此原型对象。


浅拷贝与深拷贝

原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype。Prototype类需要具备以下两个条件:


1)实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出 CloneNotSupportedException异常。


2)重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,Prototype类需要将clone方法的作用域修改为public类型。


注:Object类的clone方法只会拷贝java中的8中基本类型以及他们的封装类型,另外还有String类型。对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。


  浅拷贝(shallow copy)


被复制对象的所有变量都含有与原来的对象相同的值(仅对于简单的值类型数据),而所有的对其他对象的引用都仍然指向原来的对象。换言之,只负责克隆按值传递的数据(比如:基本数据类型、String类型)。


实体类,具体代码如下:

package com.yoodb;
public class Prototype implements Cloneable{  private String name;
 public String getName() {    return name;  }
 public void setName(String name) {    this.name = name;  }
 @Override  protected Object clone() {    // TODO Auto-generated method stub    try {      return super.clone();    } catch (CloneNotSupportedException e) {      // TODO Auto-generated catch block      e.printStackTrace();    }    return null;  }

}


测试函数,具体代码如下:

package com.yoodb;
public class TestMain {  public static void main(String[] args) {    Prototype pro = new Prototype();    pro.setName("欢迎收藏www.yoodb.com");    Prototype prot = (Prototype) pro.clone();    prot.setName("欢迎收藏www.yoodb.com");    System.out.println("original object:" + pro.getName());    System.out.println("cloned object:" + prot.getName());  }
}


运行结果如下:

original object:欢迎收藏www.yoodb.comcloned object:欢迎收藏www.yoodb.com


  深拷贝 (deep copy)


被复制对象的所有的变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,除了浅度克隆要克隆的值外,还负责克隆引用类型的数据,基本上就是被克隆实例所有的属性的数据都会被克隆出来。


实例一

实体类,具体代码如下:

package com.yoodb;
public class Prototype implements Cloneable{  private String name;
 public String getName() {    return name;  }
 public void setName(String name) {    this.name = name;  }
 @Override  protected Object clone() {    // TODO Auto-generated method stub    try {      return super.clone();    } catch (CloneNotSupportedException e) {      // TODO Auto-generated catch block      e.printStackTrace();    }    return null;  }

}
//实体二package com.yoodb;
public class NewPrototype implements Cloneable{  private String id;
 private Prototype prototype;
 public String getId() {    return id;  }
 public void setId(String id) {    this.id = id;  }
 public Prototype getPrototype() {    return prototype;  }
 public void setPrototype(Prototype prototype) {    this.prototype = prototype;  }
 @Override  protected Object clone() {    NewPrototype prot = null;    try {      prot = (NewPrototype) super.clone();      prot.prototype = (Prototype) this.getPrototype().clone();      return prot;    } catch (Exception e) {      // TODO: handle exception    }    return null;  }

}


测试函数,具体代码如下:

package com.yoodb;
public class TestMain {  public static void main(String[] args) {    //普通赋值    Prototype pro = new Prototype();    pro.setName("欢迎收藏blog.yoodb.com");    NewPrototype newPro = new NewPrototype();    newPro.setId("yoodb");    newPro.setPrototype(pro);    //克隆赋值    NewPrototype proc = (NewPrototype) newPro.clone();    proc.setId("yoodb");    proc.getPrototype().setName("欢迎收藏blog.yoodb.com");
   System.out.println("original object id:" + newPro.getId());    System.out.println("original object name:" + newPro.getPrototype().getName());
   System.out.println("cloned object id:" + proc.getId());    System.out.println("cloned object name:" + proc.getPrototype().getName());  }
}


运行结果如下:

original object id:yoodboriginal object name:欢迎收藏blog.yoodb.comcloned object id:yoodbcloned object name:欢迎收藏blog.yoodb.com


实例二

利用串行化来实现深克隆,把对象写道流里的过程是串行化(Serilization)过程;把对象从流中读出来是并行化(Deserialization)过程。


实体类,具体代码如下:

package com.yoodb;
import java.io.Serializable;
public class Prototype implements Serializable{
 private static final long serialVersionUID = 1L;
 private String name;
 public String getName() {    return name;  }
 public void setName(String name) {    this.name = name;  }
}
//实体二package com.yoodb;
import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;
public class NewPrototype implements Serializable{
 private static final long serialVersionUID = 1L;
 private String id;
 private Prototype prototype;
 public String getId() {    return id;  }
 public void setId(String id) {    this.id = id;  }
 public Prototype getPrototype() {    return prototype;  }
 public void setPrototype(Prototype prototype) {    this.prototype = prototype;  }
 protected Object deepClone(){    try {      ByteArrayOutputStream bo = new ByteArrayOutputStream();      ObjectOutputStream oo = new ObjectOutputStream(bo);      oo.writeObject(this);
     ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());      ObjectInputStream oi = new ObjectInputStream(bi);      return oi.readObject();    } catch (Exception e) {      // TODO: handle exception    }    return null;  }
}


测试函数,具体代码如下:

package com.yoodb;
public class TestMain {  public static void main(String[] args) {    //普通赋值    Prototype pro = new Prototype();    pro.setName("欢迎收藏www.yoodb.com");    NewPrototype newPro = new NewPrototype();    newPro.setId("yoodb");    newPro.setPrototype(pro);    //克隆赋值    NewPrototype proc = (NewPrototype) newPro.deepClone();    proc.setId("yoodb");    proc.getPrototype().setName("欢迎收藏www.yoodb.com");
   System.out.println("original object id:" + newPro.getId());    System.out.println("original object name:" + newPro.getPrototype().getName());
   System.out.println("cloned object id:" + proc.getId());    System.out.println("cloned object name:" + proc.getPrototype().getName());  }
}


运行结果如下:

original object id:yoodboriginal object name:欢迎收藏www.yoodb.comcloned object id:yoodbcloned object name:欢迎收藏www.yoodb.com


  克隆满足的条件


clone()方法将对象复制了一份并返还给调用者。所谓“复制”的含义与clone()方法是怎么实现的。一般而言,clone()方法满足以下的描述:


1)对任何的对象x,都有:x.clone()!=x。换言之,克隆对象与原对象不是同一个对象。


2)对任何的对象x,都有:x.clone().getClass() == x.getClass(),换言之,克隆对象与原对象的类型一样。


3)如果对象x的equals()方法定义其恰当的话,那么x.clone().equals(x)应当成立的。

原型模式使用场景

1)资源优化场景。


2)类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。


3)性能和安全要求的场景。


4)通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。


5)一个对象多个修改者的场景。


6)一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。


7)在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。


注:原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。


原型模式优缺点

原型模式的优点是允许在运行期间,由客户来注册符合原型接口的实现类型,也可以动态地改变具体的实现类型,看起来接口没有任何变化,然而运行的已经是另外一个类实例了。因为克隆一个原型就类似于实例化一个类。



原型模式的缺点是每一个类都必须配备一个克隆方法。配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类来说不是很难,而对于已经有的类不一定很容易,特别是当一个类引用不支持序列化的间接对象,或者引用含有循环结构的时候。