原型模式

 原型模式的工作原理很简单:将一个原型对象传给那个要发动创建的对象。

原型模式的核心在于如何实现克隆方法,下面将介绍两种在Java语言中常用的克隆实现方法:

1.通用实现方法

示意代码如下所示:

class ConcretePrototype implements Prototype
{
private String  attr; //成员属性
public void  setAttr(String attr)
{
    this.attr = attr;
}
public String  getAttr()
{
    return this.attr;
}
public Prototype  clone() //克隆方法
{
    Prototype  prototype = new ConcretePrototype(); //创建新对象
    prototype.setAttr(this.attr);
    return prototype;
}
}

思考

能否将上述代码中的clone()方法写成:public Prototype clone() { return this; }?给出你的理由。

个人答:不能,因为这个返回的是对象的引用不是对象本身。

在客户类中我们只需要创建一个ConcretePrototype对象作为原型对象,然后调用其clone()方法即可得到对应的克隆对象,如下代码所示:

Prototype obj1  = new ConcretePrototype();
obj1.setAttr("Sunny");
Prototype obj2  = obj1.clone();

Java语言提供的clone()方法:

需要注意的是能够实现克隆的Java类必须实现一个标识接口Cloneable,表示这个Java类支持被复制。如果一个类没有实现这个接口但是调用了clone()方法,Java编译器将抛出一个CloneNotSupportedException异常。如下代码所示:

class ConcretePrototype implements  Cloneable
{
……
public Prototype  clone()
{
  Object object = null;
  try {
     object = super.clone();
  } catch (CloneNotSupportedException exception) {
     System.err.println("Not support cloneable");
  }
  return (Prototype )object;
}
……
}

在客户端创建原型对象和克隆对象也很简单,如下代码所示:

Prototype obj1  = new ConcretePrototype();
Prototype obj2  = obj1.clone();

7.3 完整解决方案

开发人员决定使用原型模式来实现工作周报的快速创建,快速创建工作周报结构图如图7-3所示:

WeeklyLog充当具体原型类,Object类充当抽象原型类,clone()方法为原型方法。WeeklyLog类的核心代码:

public WeeklyLog clone()
       {
              Object obj = null;
              try
              {
                     obj = super.clone();
                     return (WeeklyLog)obj;     
              }
              catch(CloneNotSupportedException e)
              {
                     System.out.println("不支持复制!");
                     return null;
              }
       }

note:在实现cloneable接口的类中,实现clone方法时,切记不要调用this.clone,会发生死循环。

编写如下客户端测试代码:

 

class Client
{
       public  static void main(String args[])
       {
              WeeklyLog log_previous = new WeeklyLog();  //创建原型对象
              log_previous.setName("张无忌");
              log_previous.setDate("第12周");
              log_previous.setContent("这周工作很忙,每天加班!");

              System.out.println("****周报****");
              System.out.println("周次:" +  log_previous.getDate());
              System.out.println("姓名:" +  log_previous.getName());
              System.out.println("内容:" +  log_previous.getContent());
              System.out.println("--------------------------------");

              WeeklyLog  log_new;
              log_new  = log_previous.clone(); //调用克隆方法创建克隆对象
              log_new.setDate("第13周");
              System.out.println("****周报****");
              System.out.println("周次:" + log_new.getDate());
              System.out.println("姓名:" + log_new.getName());
              System.out.println("内容:" + log_new.getContent());
       }
}

7.4 带附件的周报

 如果使用上述原型模式来复制周报,周报虽然可以复制,但是周报的附件并不能复制

先介绍一下两种不同的克隆方法,浅克隆(ShallowClone)和深克隆(DeepClone)。

1.浅克隆

在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制,原型对象和克隆对象的成员变量指向相同的内存地址。

图7-4 浅克隆示意图

 note:

引用类型包括类、接口、数组等复杂类型

 我们首先使用浅克隆来实现工作周报和附件类的复制,其结构如图7-5所示:

图7-5 带附件的周报结构图(浅克隆)

客户端代码如下所示:

 

class Client
{
       public  static void main(String args[])
       {
              WeeklyLog  log_previous, log_new;
              log_previous  = new WeeklyLog(); //创建原型对象
              Attachment  attachment = new Attachment(); //创建附件对象
              log_previous.setAttachment(attachment);  //将附件添加到周报中
              log_new  = log_previous.clone(); //调用克隆方法创建克隆对象
              //比较周报
              System.out.println("周报是否相同? " + (log_previous ==  log_new));
              //比较附件
              System.out.println("附件是否相同? " +  (log_previous.getAttachment() == log_new.getAttachment()));
       }
}

由于使用的是浅克隆技术,因此工作周报对象复制成功,通过“==”比较原型对象和克隆对象的内存地址时输出false;但是比较附件对象的内存地址时输出true,说明它们在内存中是同一个对象。

2.深克隆

在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制

 

通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。

 

工作周报类WeeklyLog不再使用Java自带的克隆机制,而是通过序列化来从头实现对象的深克隆,我们需要重新编写clone()方法,修改后的代码如下:

 //使用序列化技术实现深克隆
       public WeeklyLog deepClone() throws  IOException, ClassNotFoundException, OptionalDataException
       {
              //将对象写入流中
              ByteArrayOutputStream bao=new  ByteArrayOutputStream();
              ObjectOutputStream oos=new  ObjectOutputStream(bao);
              oos.writeObject(this);

              //将对象从流中取出
              ByteArrayInputStream bis=new  ByteArrayInputStream(bao.toByteArray());
              ObjectInputStream ois=new  ObjectInputStream(bis);
              return  (WeeklyLog)ois.readObject();
       }

客户端代码如下所示:

 

class Client
{
       public  static void main(String args[])
       {
              WeeklyLog  log_previous, log_new = null;
              log_previous  = new WeeklyLog(); //创建原型对象
              Attachment  attachment = new Attachment(); //创建附件对象
              log_previous.setAttachment(attachment);  //将附件添加到周报中
              try
              {
                     log_new =  log_previous.deepClone(); //调用深克隆方法创建克隆对象                  
              }
              catch(Exception e)
              {
                     System.err.println("克隆失败!");
              }
              //比较周报
              System.out.println("周报是否相同? " + (log_previous ==  log_new));
              //比较附件
              System.out.println("附件是否相同? " +  (log_previous.getAttachment() == log_new.getAttachment()));
       }
}

编译并运行程序,输出结果如下:

周报是否相同?  false
附件是否相同?  false

7.5 原型管理器的引入和实现

原型管理器(Prototype Manager)是将多个原型对象存储在一个集合中供客户端使用,它是一个专门负责克隆对象的工厂,其中定义了一个集合用于存储原型对象,如果需要某个原型对象的一个克隆,可以通过复制集合中对应的原型对象来获得。

我们使用带原型管理器的原型模式实现公文管理器的设计,其结构如图7-9所示:

 

 

//原型管理器(使用饿汉式单例实现)
class  PrototypeManager
{
       //定义一个Hashtable,用于存储原型对象
       private Hashtable ht=new Hashtable();
       private static PrototypeManager pm =  new PrototypeManager();

       //为Hashtable增加公文对象   
     private  PrototypeManager()
     {
              ht.put("far",new  FAR());
              ht.put("srs",new  SRS());               
     }

     //增加新的公文对象
       public void addOfficialDocument(String  key,OfficialDocument doc)
       {
              ht.put(key,doc);
       }

       //通过浅克隆获取新的公文对象
       public OfficialDocument  getOfficialDocument(String key)
       {
              return  ((OfficialDocument)ht.get(key)).clone();
       }

       public static PrototypeManager  getPrototypeManager()
       {
              return pm;
       }
}

客户端代码如下所示:

 

class Client
{
       public  static void main(String args[])
       {
              //获取原型管理器对象
              PrototypeManager pm =  PrototypeManager.getPrototypeManager();  

              OfficialDocument  doc1,doc2,doc3,doc4;

              doc1  = pm.getOfficialDocument("far");
              doc1.display();
              doc2  = pm.getOfficialDocument("far");
              doc2.display();
              System.out.println(doc1  == doc2);

              doc3  = pm.getOfficialDocument("srs");
              doc3.display();
              doc4  = pm.getOfficialDocument("srs");
              doc4.display();
              System.out.println(doc3  == doc4);
       }
}

编译并运行程序,输出结果如下:

《可行性分析报告》
《可行性分析报告》
false
《软件需求规格说明书》
《软件需求规格说明书》
false

 

 
posted @ 2019-01-09 17:00  Archer-Fang  阅读(182)  评论(0编辑  收藏  举报