设计模式:(4)原型模式(Prototype)

今天我想分享一下与克隆有关的一个设计模式: 原型模式(Prototype)

原型模式可能是创建型设计模式中最简单的一个了,但是要真正理解原型设计模式的核心思想并且将其用到系统设计当中可能并不像想象中的那么简单。

在还没有将类作为类对象的C++语言中使用原型模式可能会在克隆对象中获益很多,因为在系统运行中,对于一个C++对象,只能得到很少或者得不到任何类型信息,所以不能根据对象获取类再创建对象。对于任何支持克隆的编程语言,运行中重复的创建结构上差不多的繁杂的、嵌套的和复合的对象并初始化它们永远也比在创建对象时提供一个克隆接口并在合适的时候调用这个克隆接口浪费更多时间和系统资源。因为创建一个对象的时间远大于克隆一个对象的时间,后者只是在内存进行操作。当然在C++中创建克隆接口也会是一件比较复杂的事情,不过这不是我今天分享的重点,我今天就以java语言为基础来分享原型设计模式在java开发中的使用方式(同样适用于其它面向对象编程语言),幸好的是在java中每个类都默认继承Object类而这个类中就默认提供了一个Clone方法,使用该方法就可以克隆对象自身。

在开始本篇分享之前,我依然将设计模式的分类图贴出来,方便查看。


意图用原型实例指定创建对象的种类(每个原型就是一个类),并且通过拷贝(克隆,下文中这两个概念通用)这些原型来创建新对象。

动机有时候我们可能遇到这种情况,一个复杂的对象可能已经经过各种对象复合完成,但是系统却要重复的创建同样的对象,并且这些对象的结构一模一样仅仅有可能其中部分的状态不同,如果采用New对象的方式来重新创建一个单一或者复合对象对于系统来说代价很高。这时候就可以就会考虑用原型模式。就像一个射击类游戏里面所有的人物角色、各种武器以及各种障碍物都可以作为一个原型由统一的原型管理器管理。每次初始化一个游戏场景,只需要从原型管理器获取不同的原型并拷贝原型来创建数个己方的人物角色和数个敌方的人物角色,而这些角色可能仅仅只是各自物携带的武器、生命值等的不同。这种方式对于拷贝一个游戏场景更加的有效——可能我们都不需要重新创建一个场景而只是复制一下场景就可以避免复杂的初始化、嵌套和复合,当然这可能会涉及到深拷贝的知识,后面会详细解释。

此外我们甚至可以进一步使用Prototype模式来减少类的数目。比如说射击类游戏中的某两型枪械基本相似只是因为口径不同需要使用不同的子弹,所以我们可以初始化一个大口径的枪械,然后在需要的时候将小口径的子弹作为初始化参数传入复制的大口径的枪械对象,这样原本作为大口径的对象现在就可以作为小口径枪械对象使用了(注意:这种方式最好有一个更高一级的接口作为对象的引用)。

分类对象创建型模式

适用情况 当一个系统应该独立于它的产品创建、构成和表示时,要使用Prototype模式;以及

  • 当要实例化的类是在运行时刻指定时,例如,通过动态装载(java采用Class.forName("class name")来动态载入类。如果在适当的时候把这个类注册成原型,然后在运行当中从原型获取克隆对象,那么就不需要动态载入类的过程了);或者

  • 为了避免创建一个与产品类层次平行的工厂类层次时(因为原型仅仅被唯一的原型管理器管理,这样原型就相当于已经产生的产品,以后需要某个产品的时候就直接克隆这个原型就可以了,这相比于每个产品都需要一个工厂类来说,少了Factory层次的对象);或者

  • 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

结构UML图


从类图中可以看到原型模式是由三种角色组成,其中:

  • Prototype 声明一个克隆自身的接口。

  • ConcreteProtorype 实现一个克隆自身的操作。

  • Client 让一个原型克隆自身从而创建一个新的对象。

协 作: 客户请求一个原型克隆自身。

效果:  Prototype 有许多和Abstract Factory和Builder一样的效果:它对客户隐藏了具体的产品类(比如我们只需要在射击游戏加载的时候初始化一次游戏中各个人物角色和武器及障碍物的原型,然后需要的时候就用原型管理器复制一个原型的副本即可),因此减少了客户知道类的名字的数目。下面列出在java以及支持克隆的面向对象语言中使用Protorype模式的另外一些优点。

1 ) 运行时刻增加和删除产品:倘若我们在一个复杂的应用中将各种需要频繁创建对象的类(有些重复创建的对象可能只是内部状态不同)的一个实例注册为一个原型,并创建一个原型管理器,提供增加和删除原型的方法,那么当我们需要创建新的对象的时候,如果原型管理器中存在这个对象就可以调用指定原型的Clone()方法来复制一个对象即可,如果不再需要这个原型,可以使用原型管理器的删除方法删除原型即可。

2) 改变结构以指定新对象: 许多应用由简单子对象和复合子对象来创建对象。比如射击类游戏中往往会有一个复杂的工事出现,如果这个工事的创建需要很多掩体组成,而对于一个掩体来说又是有许多其它防御设备组成,比如说掩体墙、陷阱和相关防御武器。如果场景里面的掩体在游戏中会不断的被摧毁,然后又创建相同的或者只是内部部分特征不同的掩体(如:可能掩体墙壁由普通的墙壁变成了可抵挡坦克炮的高强度墙壁),那么将不同种类的掩体作为一个原型注册到原型管理器。重复使用掩体原型构建的不同的工事只要实现一个深复制,具有不同结构的工事也可以是原型了,以后要构建相同结构的工事就直接用原型复制就可以了。

实现 : 当实现原型时,要考虑下面的一些问题:

1 ) 使用一个原型管理器:当一个系统中原型数目不固定时(也就是说,它们可以动态创建和销毁),要保持一个可用的原型管理器(通常是一个工厂类)。客户不会自己来管理原型,但会在原型管理器中存储和检索原型。客户在克隆一个原型前会向原型管理器请求该原型。 原型管理器是一个关联存储器,它返回一个与给定关键字相匹配的原型(如工厂类里面用一个Map对象来保存原型)。它有一些操作可以用来通过关键字向原管理器增加原型和从原型管理器删除注册。客户可以在运行时更改甚或浏览这个原管理器。这使得客户无需编写代码就可以扩展并得到系统清单。

2 ) 实现克隆操作: Prototype 模式最困难的部分在于正确实现Clone操作。当对象结构包含循环引用时,这尤为棘手。 大多数语言都对克隆对象提供了一些支持。例如, java语言提供了一个Clone的实现,它被所有Object的子类所继承,但是这个方法是受保护的(protected 访问权限),使用前需要重写并开放为public。实现真真的克隆,需要考虑,克隆一个对象是依次克隆它的实例变量呢,或者还是由克隆对象和原对象共享这些变量? 浅拷贝简单并且通常也足够了,它是 java所缺省提供的。简单拷贝意味着在拷贝的和原来的对象之间是共享成员对象的。但克隆一个结构复杂的原型通常需要深拷贝,因为复制对象和原对象必须相互独立。因此你必须保证克隆对象的构件也是对原型的构件的克隆。克隆迫使你决定如果所有东西都被共享了该怎么办。如,对于一个包含对象成员变量的对象,如果只是浅拷贝可能那个对象成员变量就可能和原本的对象引用相同的对象,这时候成员对象就被共享了。当然如果我们采用串行流的方式将一个实现了Serializable的对象持久化到本地磁盘,然后采用流的方式读取这个对象可能就不需要考虑这个问题。

3) 初始化克隆对象: 当采用克隆的方式复制对象的时候,可能这个对象就是客户端需要的对象,也有可能这个对象不是客户端最终需要的对象,但是客户端只要稍作修改即初始化克隆对象的内部状态就可达到目的。因为克隆操作不可能调用构造函数所以有必要在需要克隆的对象中提供一个初始化方法来初始化内部状态是可取的唯一办法。注意深拷贝 Clone操作时,一些复制在你重新初始化它们之前可能必须要被删除掉(删除可以显式地做也可以在初始化方法内部做)。

标准代码(浅拷贝):

package com.code2note.prototype;

class Child{

       private String name;

       public Child(String name){

              this.name=name;

       }

       public void setName(String name) {

              this.name = name;

       }

       public void say(){

              System.out.println("Child Object Name:"+name);

       }

       public Object clone() throws CloneNotSupportedException {

              return super.clone();

       }

}

 

class Prototype implements Cloneable{

       private Child child;

       private String name;

       public Prototype(String name1,String name2){

              this.name=name1;

              child = new Child(name2);

       }

       public Child getChild(){

              return this.child;

       }

       public void sayChild() {

              child.say();

       }

       public void say(){

              System.out.println("This Object Name: "+this.name);

       }

       public void setName(String name){

              this.name=name;

       }

       public Object clone() throws CloneNotSupportedException{

              return super.clone();

       }

}

 

public class ClientPrototype{

       public static void main(String[] args)

              throws CloneNotSupportedException {

              Prototype prototype=newPrototype("main","member");

              Prototype clone1=(Prototype)prototype.clone();

              Prototype clone2=(Prototype)prototype.clone();




              //1

              System.out.println("输出对象");

              System.out.println(prototype);

              System.out.println(clone1);

              System.out.println(clone2);

             

              //2

              System.out.println("输出成员对象");

              System.out.println(prototype.getChild());

              System.out.println(clone1.getChild());

              System.out.println(clone2.getChild());

       }

}

输出结果:

输出对象
com.pattern.prototype.Prototype@11b75be2
com.pattern.prototype.Prototype@1cf15b84
com.pattern.prototype.Prototype@29af45f4
输出成员对象
com.pattern.prototype.Child@3219ab8d
com.pattern.prototype.Child@3219ab8d
com.pattern.prototype.Child@3219ab8d

由输出可以看到当我们采用浅复制的时候,成员对象是没有被复制的(引用相同),即被原型和各个clone对象共享了。

标准代码(深拷贝):

package com.pattern.prototype;

class Child implements Cloneable{

   private String name;

   public Child(String name){

      this.name=name;

   }

   publicvoid setName(String name) {

      this.name = name;

   }

   publicvoid say(){

      System.out.println("Child Object Name:"+name);

   }

   public Object clone() throws CloneNotSupportedException {

      returnsuper.clone();

   }

}

 

class Prototype implements Cloneable{

   private Child child;

   private String name;

   public Prototype(String name1,String name2){

      this.name=name1;

      child = new Child(name2);

   }

   public Child getChild(){

      returnthis.child;

   }

   public void sayChild() {

      child.say();

   }

   public void say(){

      System.out.println("This Object Name: "+this.name);

   }

   public void setName(String name){

      this.name=name;

   }

   public Object clone() throws CloneNotSupportedException{

      Prototype proto= (Prototype) super.clone();

      proto.child=(Child) this.child.clone();

      return proto;

   }

}

 

public class ClientPrototype{

   public static void main(String[] args) 

            throws CloneNotSupportedException {

      Prototype prototype=new Prototype("main","member");

      Prototype clone1=(Prototype)prototype.clone();

      Prototype clone2=(Prototype)rototype.clone();

      

      System.out.println("输出对象");

      System.out.println(prototype);

      System.out.println(clone1);

      System.out.println(clone2);

      

      System.out.println("输出成员对象");

      System.out.println(prototype.getChild());

      System.out.println(clone1.getChild());

      System.out.println(clone2.getChild());

   }

}

输出结果:

输出对象
com.pattern.prototype.Prototype@1cf15b84
com.pattern.prototype.Prototype@29af45f4
com.pattern.prototype.Prototype@3219ab8d
输出成员对象
com.pattern.prototype.Child@334dcfad
com.pattern.prototype.Child@397d812b
com.pattern.prototype.Child@5eab4b89

从输出部分可以看到采用深复制后,原型的成员对象和克隆的各个对象的成员对象的引用地址也不相同了,所以完整的复制了Prototype的对象。

标准代码(使用对象输出流复制对象):

package ...


class Child implements Serializable{

   private String name;

   public Child(String name){

      this.name=name;

   }

   public void setName(String name) {

      this.name = name;

   }

   public void say(){

      System.out.println("Child Object Name:"+name);

   }

}

 

class Prototype implements Serializable{

   private Child child;

   private String name;

   private List
   
   
    
     protoList;

   public Prototype(String name1,String name2){

      this.name=name1;

      child = new Child(name2);

      this.protoList=new ArrayList
    
    
     
     ();

      protoList.add(child);

   }

   public Child getChild(){

      returnthis.child;

   }

   public Child getChildListFirst(){

      return this.protoList.get(0);

   }

   public void sayChild() {

      child.say();

   }

   public void say(){

      System.out.println("This Object Name: "+this.name);

   }

   public void sayChildListFirst(){

      this.protoList.get(0).say();

   }

   public void setName(String name){

      this.name=name;

   }

}

 

public class ClientPrototype{

   public static void main(String[] args)

          throws CloneNotSupportedException {

      Prototype prototype=new Prototype("main","member");

      ObjectOutputStream oou=null;

      ObjectInputStream oin=null;

      try {

        

        //将串行化的原型写到本地磁盘

        oou=new ObjectOutputStream(

            new FileOutputStream(new File("D:/prototype.txt")));

        oou.writeObject(prototype);

        

        //读取一个串行化对象

        oin=new ObjectInputStream(

              new FileInputStream(new File("D:/prototype.txt")));

        Prototype clone1=(Prototype) oin.readObject();

        

        //读出第二个串行化对象

        oin=new ObjectInputStream(

           new FileInputStream(new File("D:/prototype.txt")));

        Prototype clone2=(Prototype) oin.readObject();

        

        //开始测试效果

        System.out.println("输出对象");

        System.out.println(prototype);

        System.out.println(clone1);

        System.out.println(clone2);

        

        System.out.println("输出成员对象");

        System.out.println(prototype.getChild());

        System.out.println(clone1.getChild());

        System.out.println(clone2.getChild());

        

        System.out.println("输出成员list中的对象");

         System.out.println(prototype.getChildListFirst());

         System.out.println(clone1.getChildListFirst());

         System.out.println(clone2.getChildListFirst());

        

      } catch (Exception e) {

        e.printStackTrace();

      }finally{

        try {

           oou.flush();

           oin.close();

        } catch (IOException e) {

           e.printStackTrace();

        }

      }

   }

}

    
    
   
   

输出结果:

输出对象
com.pattern.prototype.Prototype@352e71c4
com.pattern.prototype.Prototype@503f0b70
com.pattern.prototype.Prototype@5b080f38
输出成员对象
com.pattern.prototype.Child@7c2f1622
com.pattern.prototype.Child@6e1f5438
com.pattern.prototype.Child@4ad26103
输出成员list中的对象

com.pattern.prototype.Child@7c2f1622
com.pattern.prototype.Child@6e1f5438
com.pattern.prototype.Child@4ad26103


​------------

原型模式比较简单,特别是对于面向对象的java编程语言,所以我也不写示例代码了。这些都是我对原型模式的理解,我觉得GOF在《设计模式》中特别提到原型管理器,所以如何将有效的类对象组织到原型管理器也很关键。

喜欢我的分享的朋友可以关注我的公众号code2note或者搜索"程序员日记"关注,当然欢迎大家反馈。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值