Loading...
墨滴

千羽的编程时光

2021/10/21  阅读:34  主题:嫩青

07-原型模式

  • Gitee:https://gitee.com/nateshao/design-demo/tree/main/JavaDesignPatterns/07-prototype

  • Github:https://github.com/nateshao/design-demo/tree/main/JavaDesignPatterns/07-prototype

1. 原型模式概述

通过复制一个原型对象得到多个与原型对象一模一样的新对象

定义:

原型模式:使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象

  • 工作原理:将一个原型对象传给要发动创建的对象(即客户端对象),这个要发动创建的对象通过请求原型对象复制自己来实现创建过程
  • 创建新对象(也称为克隆对象)的工厂就是原型类自身,工厂方法由负责复制原型对象的克隆方法来实现
  • 通过克隆方法所创建的对象是全新的对象,它们在内存中拥有新的地址,每一个克隆对象都是独立的
  • 通过不同的方式对克隆对象进行修改以后,可以得到一系列相似但不完全相同的对象

2. 原型模式的结构与实现

原型模式的结构

原型模式包含以下3个角色:

  • Prototype(抽象原型类)
  • ConcretePrototype(具体原型类)
  • Client(客户类)

浅克隆与深克隆

**浅克隆(Shallow Clone):**当原型对象被复制时,只复制它本身和其中包含的值类型的成员变量,而引用类型的成员变量并没有复制。

**深克隆(Deep Clone):**除了对象本身被复制外,对象所包含的所有成员变量也将被复制。

通用的克隆实现方法

Java语言中的clone()方法和Cloneable接口

在Java语言中,提供了一个clone()方法用于实现浅克隆,该方法使用起来很方便,直接调用super.clone()方法即可实现克隆

浅克隆

WeeklyLog.java

package com.nateshao.prototype.shallowclone;

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

Attachment.java

package com.nateshao.prototype.shallowclone;

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

Client.java

package com.nateshao.prototype.shallowclone;

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

深克隆

WeeklyLog.java

package com.nateshao.prototype.deepclone;

import java.io.*;

/**
 * @date Created by 邵桐杰 on 2021/10/14 21:59
 * @微信公众号 程序员千羽
 * @个人网站 www.nateshao.cn
 * @博客 https://nateshao.gitee.io
 * @GitHub https://github.com/nateshao
 * @Gitee https://gitee.com/nateshao
 * Description:
 */

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

Attachment.java

package com.nateshao.prototype.deepclone;

import java.io.Serializable;

/**
 * @date Created by 邵桐杰 on 2021/10/14 22:00
 * @微信公众号 程序员千羽
 * @个人网站 www.nateshao.cn
 * @博客 https://nateshao.gitee.io
 * @GitHub https://github.com/nateshao
 * @Gitee https://gitee.com/nateshao
 * Description:
 */


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

Client.java

package com.nateshao.prototype.deepclone;

/**
 * @date Created by 邵桐杰 on 2021/10/14 22:00
 * @微信公众号 程序员千羽
 * @个人网站 www.nateshao.cn
 * @博客 https://nateshao.gitee.io
 * @GitHub https://github.com/nateshao
 * @Gitee https://gitee.com/nateshao
 * Description:
 */

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

3. 原型模式的应用实例

实例说明

在使用某OA系统时,有些岗位的员工发现他们每周的工作都大同小异,因此在填写工作周报时很多内容都是重复的,为了提高工作周报的创建效率,大家迫切希望有一种机制能够快速创建相同或者相似的周报,包括创建周报的附件。

试使用原型模式对该OA系统中的工作周报创建模块进行改进。

实例类图

工作周报创建模块结构图:浅克隆
工作周报创建模块结构图:浅克隆

工作周报对象被成功复制,但是附件对象并没有复制,实现了浅克隆

深克隆解决方案

工作周报类WeeklyLog和附件类Attachment实现Serializable接口

修改工作周报类WeeklyLog的clone()方法

  1. WeeklyLog: 具体原型类
  2. Attachment: 具体原型类
  3. Client

周报是否相同? false

附件是否相同? false

工作周报对象和附件对象都成功复制,实现了深克隆

4. 原型管理器

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

结构:

带原型管理器的原型模式
带原型管理器的原型模式

实现

import java.util.*;

public class PrototypeManager {
    private Hashtable prototypeTable=new Hashtable();  //使用Hashtable存储原型对象
    public PrototypeManager() {
        prototypeTable.put("A"new ConcretePrototypeA());
        prototypeTable.put("B"new ConcretePrototypeB());
    }
   
    public void add(String key, Prototype prototype) {
        prototypeTable.put(key,prototype);
    } 

    public Prototype get(String key) {
        Prototype clone = null;
        clone = ((Prototype) prototypeTable.get(key)).clone(); //通过克隆方法创建新对象
        return clone;
    }
}

5. 原型模式的优缺点与适用环境

模式优点

  1. 简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率
  2. 扩展性较好
  3. 提供了简化的创建结构,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品
  4. 可以使用深克隆的方式保存对象的状态,以便在需要的时候使用,可辅助实现撤销操作

模式缺点

  1. 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了开闭原则
  2. 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦

模式适用环境

  1. 创建新对象成本较大,新对象可以通过复制已有对象来获得,如果是相似对象,则可以对其成员变量稍作修改
  2. 系统要保存对象的状态,而对象的状态变化很小
  3. 需要避免使用分层次的工厂类来创建分层次的对象
  4. Ctrl + C -> Ctrl + V

千羽的编程时光

2021/10/21  阅读:34  主题:嫩青

作者介绍

千羽的编程时光