1.模式定义:

  原型模式就是用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。

2.使用场景:

  在原型模式中我们可以利用过一个原型对象来指明我们所要创建对象的类型,然后通过复制这个对象的方法来获得与该对象一模一样的对象实例。这就是原型模式的设计目的。
  (1). 如果创建新对象成本较大,我们可以利用已有的对象进行复制来获得。
  (2).  如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占内存不大的时候,也可以使用原型模式配合备忘录模式来应用。相反,如果对象的状态变化很大,或者对象占用的内存很大,那么采用状态模式会比原型模式更好。
  (3).需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。

3.模式实现:

  (1)简单形式:

三个角色:

  [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;
  }
}

  (2)登记形式

  四个角色:

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

public class Client {
  public static void main(String[]args){
    try{
      Prototype p1 = new ConcretePrototype1();
      PrototypeManager.setPrototype("p1", p1);
      //获取原型来创建对象
      Prototype p3 = PrototypeManager.getPrototype("p1").clone();
      p3.setName("张三");
      System.out.println("第一个实例:" + p3);
      //有人动态的切换了实现
      Prototype p2 = new ConcretePrototype2();
      PrototypeManager.setPrototype("p1", p2);
      //重新获取原型来创建对象
      Prototype p4 = PrototypeManager.getPrototype("p1").clone();
      p4.setName("李四");
      System.out.println("第二个实例:" + p4);
      //有人注销了这个原型
      PrototypeManager.removePrototype("p1");
      //再次获取原型来创建对象
      Prototype p5 = PrototypeManager.getPrototype("p1").clone();
      p5.setName("王五");
      System.out.println("第三个实例:" + p5);
    }catch(Exception e){
      e.printStackTrace();
    }
  }
}

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

public interface Prototype{
  public Prototype clone();
  public String getName();
  public void setName(String name);
}

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

public class ConcretePrototype1 implements Prototype {
  private String name;
  public Prototype clone(){
    ConcretePrototype1 prototype = new ConcretePrototype1();
    prototype.setName(this.name);
    return prototype;
  }
  public String toString(){
    return "Now in Prototype1 , name = " + this.name;
  }
  @Override
  public String getName() {
    return name;
  }
  @Override
  public void setName(String name) {
    this.name = name;
  }
  }
public class ConcretePrototype2 implements Prototype {
  private String name;
  public Prototype clone(){
    ConcretePrototype2 prototype = new ConcretePrototype2();
    prototype.setName(this.name);
    return prototype;
  }
  public String toString(){
    return "Now in Prototype2 , name = " + this.name;
  }
  @Override
  public String getName() {
    return name;
  }
  @Override
  public void setName(String name) {
    this.name = name;
  }
}

    [4].原型管理器(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;
  }
}

  (3).浅克隆

     只负责克隆按值传递的数据(比如基本数据类型、String类型),而不复制它所引用的对象,换言之,所有的对其他对象的引用都仍然指向原来的对象。
    示例:齐天大圣浅克隆: 拥有自己和金箍棒两个对象

    [1].金箍棒:GoldRingedStaff.java

public class GoldRingedStaff {
  private float height = 100.0f;
  private float diameter = 10.0f;
  /**
  * 增长行为,每次调用长度和半径增加一倍
  */
  public void grow(){
    this.diameter *= 2;
    this.height *= 2;
  }
  /**
  * 缩小行为,每次调用长度和半径减少一半
  */
  public void shrink(){
    this.diameter /= 2;
    this.height /= 2;
  }
}

    [2].大圣本身:Monkey.java

public class Monkey implements Cloneable {
  //身高
  private int height;
  //体重
  private int weight;
  //生日
  private Date birthDate;
  //金箍棒
  private GoldRingedStaff staff;
  /**
  * 构造函数
  */
  public Monkey(){
    this.birthDate = new Date();
    this.staff = new GoldRingedStaff();
  }
  /**
  * 克隆方法
  */
  public Object clone(){
    Monkey temp = null;
    try {
      temp = (Monkey) super.clone();
    } catch (CloneNotSupportedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } finally {
      return temp;
    }
  }
  public int getHeight() {
    return height;
  }
  public void setHeight(int height) {
    this.height = height;
  }
  public int getWeight() {
    return weight;
  }
  public void setWeight(int weight) {
    this.weight = weight;
  }
  public Date getBirthDate() {
    return birthDate;
  }
  public void setBirthDate(Date birthDate) {
    this.birthDate = birthDate;
  }
  public GoldRingedStaff getStaff() {
    return staff;
  }
  public void setStaff(GoldRingedStaff staff) {
    this.staff = staff;
  }
}

    [3].克隆实现:TheGreatestSage.java

public class TheGreatestSage {
  private Monkey monkey = new Monkey();
  public void change(){
    //克隆大圣本尊
    Monkey copyMonkey = (Monkey)monkey.clone();
    System.out.println("大圣本尊的生日是:" + monkey.getBirthDate());
    System.out.println("克隆的大圣的生日是:" + monkey.getBirthDate());
    System.out.println("大圣本尊跟克隆的大圣是否为同一个对象 " + (monkey == copyMonkey));
    System.out.println("大圣本尊持有的金箍棒 跟 克隆的大圣持有的金箍棒是否为同一个对象? " + (monkey.getStaff() == copyMonkey.getStaff()));
  }
  public static void main(String[]args){
    TheGreatestSage sage = new TheGreatestSage();
    sage.change();
  }
}

  (4).深克隆

    除了浅度克隆要克隆的值外,还负责克隆引用类型的数据。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。
    换言之,深度克隆把要复制的对象所引用的对象都复制了一遍,而这种对被引用到的对象的复制叫做间接复制。
    在采取深度克隆时,需要决定多深才算深。此外,在深度克隆的过程中,很可能会出现循环引用的问题,必须小心处理。
    利用序列化实现深度克隆
    把对象写到流里的过程是序列化(Serialization)过程;而把对象从流中读出来的过程则叫反序列化(Deserialization)过程。应当指出的是,写到流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。
    在Java语言里深度克隆一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的拷贝)写到一个流里(序列化),再从流里读回来(反序列化),便可以重建对象。
 
    示例:齐天大圣深克隆: 拥有自己和金箍棒两个对象

    [1].金箍棒:GoldRingedStaff.java

public class GoldRingedStaff implements Serializable{
  private float height = 100.0f;
  private float diameter = 10.0f;
  /**
  * 增长行为,每次调用长度和半径增加一倍
  */
  public void grow(){
    this.diameter *= 2;
    this.height *= 2;
  }
  /**
  * 缩小行为,每次调用长度和半径减少一半
  */
  public void shrink(){
    this.diameter /= 2;
    this.height /= 2;
  }
}

    [2].大圣本身:Monkey.java

public class Monkey implements Cloneable,Serializable {
  //身高
  private int height;
  //体重
  private int weight;
  //生日
  private Date birthDate;
  //金箍棒
  private GoldRingedStaff staff;
  /**
  * 构造函数
  */
  public Monkey(){
    this.birthDate = new Date();
    staff = new GoldRingedStaff();
  }
  /**
  * 克隆方法
  */
  public Object clone(){
    Monkey temp = null;
    try {
      temp = (Monkey) super.clone();
    } catch (CloneNotSupportedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } finally {
      return temp;
    }
  }
  public Object deepClone() throws IOException, ClassNotFoundException{
    //将对象写到流里
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(this);
    //从流里读回来
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);
    return ois.readObject();
  }
  public int getHeight() {
    return height;
  }
  public void setHeight(int height) {
    this.height = height;
  }
  public int getWeight() {
    return weight;
  }
  public void setWeight(int weight) {
    this.weight = weight;
  }
  public Date getBirthDate() {
    return birthDate;
  }
  public void setBirthDate(Date birthDate) {
    this.birthDate = birthDate;
  }
  public GoldRingedStaff getStaff() {
    return staff;
  }
  public void setStaff(GoldRingedStaff staff) {
    this.staff = staff;
  }
}

    [3].克隆实现:TheGreatestSage.java

public class TheGreatestSage {
  private Monkey monkey = new Monkey();   public void change() throws IOException, ClassNotFoundException{
    Monkey copyMonkey = (Monkey)monkey.deepClone();
    System.out.println("大圣本尊的生日是:" + monkey.getBirthDate());
    System.out.println("克隆的大圣的生日是:" + monkey.getBirthDate());
    System.out.println("大圣本尊跟克隆的大圣是否为同一个对象 " + (monkey == copyMonkey));
    System.out.println("大圣本尊持有的金箍棒 跟 克隆的大圣持有的金箍棒是否为同一个对象? " + (monkey.getStaff() == copyMonkey.getStaff()));
  }
  public static void main(String[]args) throws IOException, ClassNotFoundException{
    TheGreatestSage sage = new TheGreatestSage();
    sage.change();
  }
}

4.优缺点:

  (1)原型模式的优点

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

  (2)原型模式的缺点

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

设计模式(1)--Prototype(原型模式)--创建型的更多相关文章

  1. Prototype原型模式(创建型模式)

    1.原型模式解决的问题 现在有一个抽象的游戏设施建造系统,负责构建一个现代风格和古典风格的房屋和道路. 前提:抽象变化较慢,实现变化较快(不稳定) 整个抽象的游戏设施建造系统相对变化较慢,本例中只有一 ...

  2. 设计模式(五):PROTOTYPE原型模式 -- 创建型模式

    1.定义 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. 2.适用场景 原型模式的主要思想是基于现有的对象克隆一个新的对象出来,一般是有对象的内部提供克隆的方法,通过该方法返回一个对 ...

  3. C#面向对象设计模式纵横谈——6.Prototype 原型模式(创建型模式)

    动机(Motivation) 在软件系统中,经常面临着“某些结构复杂的对象”的创建工作.由于需求的变化,这些对象经常面临着剧烈的变化,但他们却拥有比较稳定一致的接口. 如何应对这种变化?如何向“客户程 ...

  4. 设计模式<1>------单例模式和原型模式------创建型

    原文引自:http://www.cnblogs.com/lonelyxmas/p/3720808.html 单例模式 单例模式就是保证在整个应用程序的生命周期中,在任何时刻,被指定的类只有一个实例,并 ...

  5. Prototype原型(创建型模式)

    依赖关系的倒置:抽象不应该依赖于实现的细节,实现细节应该依赖于抽象. 原型模式的定义 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.prototype模式允许一个对象再创建另外一个可 ...

  6. Java设计模式:Prototype(原型)模式

    概念定义 使用原型实例指定待创建对象的种类,并通过拷贝该原型来创建新的对象.Prototype模式允许一个原型对象克隆(复制)出多个与其相同的对象,而无需知道任何如何创建的细节. 应用场景 对象的创建 ...

  7. 设计模式(2)--Singleton--单例模式--创建型

    1.模式定义: 单例模式确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例. 2.模式特点: (1)单例类只能有一个实例. (2)单例类必须自己创建自己的唯一实例. (3)单例类必须给所有 ...

  8. 设计模式(5)--Builder(建造模式)--创建型

    1.模式定义: 建造模式是对象的创建模式.建造模式可以将一个产品的内部表象(internal representation)与产品的生产过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品 ...

  9. FactoryMethod工厂方法模式(创建型模式)

    1.工厂方法模式解决的问题 现在有一个抽象的游戏设施建造系统,负责构建一个现代风格和古典风格的房屋和道路. 前提:抽象变化较慢,实现变化较快(不稳定) 整个抽象的游戏设施建造系统相对变化较慢,本例中只 ...

随机推荐

  1. work1-英语辅导班在线报名系统

    作品简述: 这是一个英语辅导班在线报名系统,目的是提供一个供学生报名辅导班的平台,也同时为老师收集报名信息提供便利. 使用的语言: php+html+js 服务器: 新浪sae服务器,apache 数 ...

  2. 你猜这个题输出啥?-- java基础概念

    最近在看java编程思想,大部分内容都觉得没啥意思,但是突然看到一个基本概念(似乎都忘了),于是写了测试题,我想这辈子也不会忘这个概念了. 题目如下: public class Suber exten ...

  3. 文档模型(JSON)使用介绍

    一.背景 E.F.Codd在1970年首次提出了数据库系统的关系模型,从此开创了数据库关系方法和关系数据理论的研究,为数据库技术奠定了理论基础,数据库技术也开始蓬勃发展.而随着几大数据库厂商陆续发布的 ...

  4. 进程间通信之AIDL

    一.引言 AIDL是android内部进程通信接口的描述语言,是实现跨进程方法调用的一大利器,其中Binder和Messenger的实现机制都是AIDL. 二.使用下面结合示例说明其使用过程: 本次示 ...

  5. 安装WIA组件

    下载地址: http://pan.baidu.com/s/1bnGU5Nx 安装方法: 将下载后的WIAAutSDK.zip解压,复制wiaaut.dll到C:\Windows\System32,注册 ...

  6. npm的理解

    一 概念方面 npm的全称是node package manger ,是一个nodejs包管理工具,已经成为非官方的发布node模块包的标准.有了npm可以很快速的找到特定服务器要使用的包,进行下载, ...

  7. MyBatis基础

    一.大配置 <?xml version="1.0" encoding="UTF-8" ?> <!--文档类型,头文件--> <!D ...

  8. 企业QQ客服的添加

    商城项目需要用腾讯的企业QQ客服,但默认提供的是一个链接,点击后弹出一个新页面, 请确认 确认打开QQ与营销QQ4009928310对话? 点确认才弹出聊天窗口,并不是需求想要的效果,经过一番折腾联系 ...

  9. jmeter-命令行执行脚本

    日常测试过程中发现,在大数量并发时,jmeterGUI界面时长宕机.卡死,在这种情况下我们就需要使用命令行来执行脚本了(非GUI), 命令行执行首先就必须要配置环境变量,如同JAVA-HOME一样,这 ...

  10. JavaScript DOM编程艺术读后感(1)—— 平稳退化

    最近,在读<JavaScript DOM编程艺术(第二版)>这本书,想着将自己的读后感记录下来,作为记忆吧. 其实我并不是最近才刚开始读这本书的,我读了有一段时间了.我是一名web前端开发 ...