原型模式

1. 原型模式概述

原型模式指:通过一个原型对象克隆出多个一模一样的对象。

在使用原型模式时,首先需要创建一个原型对象,再通过复制这个原型对象来创建更多同类型的对象。

使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式时一种对象创建型模式。

1.1 工作原理

将一个原型对象传给要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来实现创建过程。

通过克隆方法所创建的对象是全新的,在内存中有新的地址,对克隆对象所产生的对象进行修改不会对原型对象产生任何影响。

1.2 包含角色

1.gif

  • Prototype (抽象原型类)

是声明克隆方法的接口,是所有具体类型类的公共父类。抽象类/ 接口/ 具体实现类

  • ConcretePrototype (具体原型类)

实现了抽象原型类中声明的克隆方法,在克隆方法中返回一个自己的克隆对象

  • Client

让一个原型对象克隆自身,从而创建一个新的对象。客户类针对抽象原型类Prototype编程,因此用户可以根据需要选择具体原型类,扩展性加强了。

2. 原型模式的实现

2.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;
    }
}

2.2 Java提供的clone()方法

所有的Java类都继承自java.lang.Object。事实上,Object提供了clone()方法,可以将一个Java对象复制一份。可以直接使用这个方法来实现克隆的。

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

Java中的clone()满足:

  1. 对于任何对象,都有x.clone() != x,即克隆方法创建了新的对象
  2. 对于任何对象,都有x.clone().getClass() == x.getClass(),即克隆对象与原型对象的类型一样
  3. 派生类需要实现Cloneable接口

3. 原型模式实现工作周报快速创建

2.gif

快速创建周报结构图

//工作周报WeeklyLog:具体原型类,考虑到代码的可读性和易理解性,只列出部分与模式相关的核心代码

class WeeklyLog implements Cloneable
{
    private String name;
    private String date;
    private String content;

    public  void setName(String name) {
        this.name  = name;
    }

    public  void setDate(String date) {
        this.date  = date;
    }

    public  void setContent(String content) {
        this.content  = content;
    }

    public  String getName() {
        return  (this.name);
    }

    public  String getDate() {
        return  (this.date);
    }

    public  String getContent() {
        return  (this.content);
    }

     //克隆方法clone(),此处使用Java语言提供的克隆机制

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

测试代码:

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());
       }
}

4. 浅克隆与深克隆

在java中,数据类型分为值类型和引用类型,值类型包括int,double, byte, boolean, char等,引用类型包括类,接口,数组等。浅克隆与深克隆的区别在于是否支持引用类型的成员变量的复制。

4.1 浅克隆

3.gif
浅克隆示意图

如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说二者的成员变量指向了相同的内存地址。在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制

4.gif
带附件的周报结构图

附件类代码:

//附件类
class Attachment
{
    private String name; //附件名
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return this.name;
    }
    public void download() {
        System.out.println("下载附件,文件名为" + name);
    }
}

工作周报类代码

//工作周报WeeklyLog
class WeeklyLog implements Cloneable
{
     //为了简化设计和实现,假设一份工作周报中只有一个附件对象,实际情况中可以包含多个附件,可以通过List等集合对象来实现

       private Attachment attachment;
       private String name;
       private  String date;
       private  String content;

       public void setAttachment(Attachment  attachment) {
              this.attachment = attachment;
       }

       public  void setName(String name) {
              this.name  = name;
       }

       public  void setDate(String date) {
              this.date  = date;
       }

       public  void setContent(String content) {
              this.content  = content;
       }

       public Attachment  getAttachment(){
              return (this.attachment);
       }

       public  String getName() {
              return  (this.name);
       }

       public  String getDate() {
              return  (this.date);
       }

       public  String getContent() {
              return  (this.content);
       }

     //使用clone()方法实现浅克隆

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

客户端代码:

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()));
       }
}

4.2 深克隆

无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象。即深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。

5.gif
深克隆示意图

如果要实现深克隆,在Java中可以通过序列化Serialization等方式来实现。

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

6.gif
带附件的周报结构图

附件类:

import java.io.*;

//附件类

class  Attachment implements Serializable
{
       private  String name; //附件名

       public  void setName(String name)
       {
              this.name  = name;
       }

       public  String getName()
       {
              return  this.name;
       }

     public void download()
     {
            System.out.println("下载附件,文件名为" + name);
     }
}

工作周报类代码实现:

import  java.io.*;

//工作周报类
class  WeeklyLog implements Serializable
{
       private  Attachment attachment;
       private  String name;
       private  String date;
       private  String content;
       public  void setAttachment(Attachment attachment) {
              this.attachment  = attachment;
       }

       public  void setName(String name) {
              this.name  = name
       }

       public  void setDate(String date) {
              this.date  = date;
       }

       public  void setContent(String content) {
              this.content  = content;
       }

       public  Attachment getAttachment(){
              return  (this.attachment);
       }

       public  String getName() {
              return  (this.name);
       }

       public  String getDate() {
              return  (this.date);
       }

       public  String getContent() {
              return  (this.content);
       }

   //使用序列化技术实现深克隆

       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()));
       }
}

Tips: java中的Cloneable和Serializable接口的代码很简单,都是空接口,也成为标识接口,其中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能。

4.3 Java中实现深克隆的方法浅析

4.3.1 Apache Commons Lang

SerializationUtils#clone, will perform a deep copy when all classes in the object graph implement the serializable interface.

4.3.2 Json Serialization with Gson

No need for Serializable interface

gson.fromJson(gson.toJson(userA), User.class);

4.3.3 constructor

直接handmade, 用constructor生成一个新的想要的对象。

5. 原型管理器

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

7.gif
带原型管理器的原型模式图

5.1 实例-公文管理器

8.gif
公文管理器结构图

代码实现:

import java.util.*;

//抽象公文接口,也可定义为抽象类,提供clone()方法的实现,将业务方法声明为抽象方法
interface OfficialDocument extends  Cloneable
{
       public  OfficialDocument clone();
       public  void display();
}
//可行性分析报告(Feasibility Analysis Report)类
class FAR implements OfficialDocument
{
       public  OfficialDocument clone()
      {
              OfficialDocument  far = null;
              try
              {
                     far  = (OfficialDocument)super.clone();
              }
              catch(CloneNotSupportedException  e)
              {
                  System.out.println("不支持复制!");
              }
              return  far;
       }

       public  void display()
       {
         System.out.println("《可行性分析报告》");
       }
}
//软件需求规格说明书(Software Requirements Specification)类
class SRS implements OfficialDocument
{
       public  OfficialDocument clone()
       {
              OfficialDocument  srs = null;
              try
              {
                     srs  = (OfficialDocument)super.clone();
              }
              catch(CloneNotSupportedException  e)
              { 
                System.out.println("不支持复制!");
              }
              return  srs;
       }

       public  void display()
       {
             System.out.println("《软件需求规格说明书》");
       }
}

//原型管理器(使用饿汉式单例实现)
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);
       }
}

在PrototypeManager中定义了一个Hashtable类型的集合对象,使用“键值对”来存储原型对象,客户端可以通过Key(如“far”或“srs”)来获取对应原型对象的克隆对象。PrototypeManager类提供了类似工厂方法的getOfficialDocument()方法用于返回一个克隆对象。

6. 总结

6.1 优点

  1. 简化对象的创建过程
  2. 客户端可以针对抽象原型类进行编程,具体原型类写在配置文件中
  3. 技工简化的创建结构

6.2 缺点

  1. 每一个类有自己的克隆方法,且位于一个类的内部,对已有类进行改造的时候,需要修改源代码
  2. 深克隆复杂

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 stone2paul@gmail.com

文章标题:原型模式

文章字数:2.8k

本文作者:Leilei Chen

发布时间:2020-02-02, 15:02:53

最后更新:2020-02-03, 19:22:54

原始链接:https://www.llchen60.com/%E5%8E%9F%E5%9E%8B%E6%A8%A1%E5%BC%8F/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏