设计模式(5)—— 原型模式

可以通过一个原型对象,克隆出多个一模一样的对象

1. 引出问题

图中有两份工作周报,两份周报的大部分内容完全一致,只有少部分不同

原有系统在创建新的周报时,只能生成一份空白周报,需要从零开始填写,即使只是需要复制粘贴,也同样需要付出额外的工作成本

改进思路:

  1. 可以将已有的周报保存成周报模板
  2. 在创建新周报时,可以自由选择运用周报模板或者创建空周报

2. 原型模式介绍

定义:使用原型实例指定创建对象的种类,并且通过克隆这些原型来创建新的对象

工作原理:将需要克隆的原型对象传递给负责创建新对象的对象,这个对象再通过请求原型对象克隆原型对象自身,从而实现新对象的创建过程

克隆出的对象与原型对象之间的关系是完全独立的,对新对象进行修改不会影响原型对象

原型模式包含三种角色:

  1. Prototype(抽象原型类)
  2. ConcretePrototype(具体原型类)
  3. Client(客户类)

抽象原型类中定义了clone方法,具体原型类作为抽象原型类的子类,对clone方法进行实现,Client类中有一个Prototype类的prototype属性,可以通过多态,接收具体原型类的实例,通过operation方法调用clone方法,从而完成原型对象的克隆

原型模式的核心是如何对clone方法进行实现,以下给出两种经典实现方法:

2.1. 通用实现方法

public class ConcretePrototype implements Prototype{
    private String attr; // 成员变量

    public String getAttr() {
        return attr;
    }

    public void setAttr(String attr) {
        this.attr = attr;
    }

    @Override
    public Prototype clone() {
        Prototype prototype = new ConcretePrototype();
        prototype.setAttr(this.attr);
        return prototype;
    }
}

2.2. Java语言自带的clone方法

在Object类中已经写好了clone方法

public class ConcretePrototype implements Cloneable{

    @Override
    public Prototype clone() {
        
        Object object = null;
        
        try {
            object = super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println("未实现Cloneable接口");
        }
        
        return (Prototype) object;
    }
}

使用自带的clone方式需要实现Cloneable接口

3. 使用原型模式重构周报案例

WeeklyLog类代码如下

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

    public String getName() {
        return name;
    }

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

    public String getDate() {
        return date;
    }

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

    public String getContent() {
        return content;
    }

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

    @Override
    protected WeeklyLog clone() {
        Object obj = null;
        try {
            obj = super.clone();
            return (WeeklyLog) obj;
        } catch (CloneNotSupportedException e) {
            System.out.println("克隆失败");
            return null;
        }
    }
}

改造之后,可以根据已有的周报作为模板生成新周报,而无需每次都从头开始

4. 带有附件的周报

实际应用中,若周报带有附件,那么该如何运用模板模式进行克隆呢?

4.1. 浅克隆

浅克隆中,如果原型对象中的成员变量是值类型,则复制一份到新对象中;若成员变量是引用类型,则只是简单地将引用对象的地址复制一份给新对象,即原型对象的成员变量和新对象中的成员变量会指向同一个地址

Java中Object类的clone方式实际上就是一种浅克隆

作如下验证:

原型对象中添加一个引用型的成员变量attachment表示附件

public class Attachment {
    private String name; //附件名

    public String getName() {
        return name;
    }

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

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

修改原型类,原型类中添加成员变量attachment,并实现getter和setter方法

编写测试类:

public class WeeklyLogTest {
    public static void main(String[] args) {
        WeeklyLog preLog, newLog;
        preLog = new WeeklyLog();
        Attachment attachment = new Attachment();
        preLog.setAttachment(attachment);

        newLog = preLog.clone();
        System.out.println("周报是否相同?: " + (newLog == preLog));
        System.out.println("附件是否相同?: " + (newLog.getAttachment() == preLog.getAttachment()));
    }
}

测试结果如下:

4.2. 深克隆

深克隆中,无论原型对象的成员变量是值类型还是引用类型,都会复制一份新的给新对象

Java中的深克隆一般通过序列化(Serialization)的方式实现,序列化实际上就是将对象写到流中,写到流中的对象是原有对象的一个复制体,而原有对象不变。通过序列化实现的克隆,不仅可以克隆对象本身,还可以克隆对象的引用对象。

修改附件类,实现Serializable接口

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

    public String getName() {
        return name;
    }

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

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

周报类,实现Serializable接口,将克隆方法改为deepClone:

public class WeeklyLog implements Serializable {
    private String name;
    private String date;
    private String content;
    private Attachment attachment;

    public Attachment getAttachment() {
        return attachment;
    }

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

    public String getName() {
        return name;
    }

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

    public String getDate() {
        return date;
    }

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

    public String getContent() {
        return content;
    }

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

    protected WeeklyLog deepClone() throws Exception{
        //将对象写到流中
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);

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

运行测试类,结果如下:

5. 原型管理器的引入和实现

原型管理器将多个原型对象存储在集合中供客户端使用,结构如下所示

5.1. 实例 —— 公文管理器

A公司在日常办公中有许多种类型的公文需要处理, 如:《可行性分析报告》、《立项建议书》、《软件需求规格说明书》、《项目进展报告》等。可以设计一个公文管理器,在需要的时候按照不同的模板快速创建新的公文

抽象公文接口:OfficialDocument

public interface OfficialDocument extends Cloneable{
    OfficialDocument clone();
    void display();
}

《可行性分析报告》类:FAR

public class FAR implements OfficialDocument{
    @Override
    public OfficialDocument clone() {
        OfficialDocument far = null;
        try {
            far = (OfficialDocument) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
        return far;
    }

    @Override
    public void display() {
        System.out.println("可行性分析报告...");
    }
}

《软件需求规格说明书》类:SRS

public class SRS implements OfficialDocument{
    @Override
    public OfficialDocument clone() {
        OfficialDocument srs = null;
        try {
            srs = (OfficialDocument) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
        return srs;
    }

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

原型管理类:PrototypeManager(采用饿汉式单例实现)

public class PrototypeManager {
    //创建HashTable存储原型对象
    private Hashtable<String, OfficialDocument> ht = new Hashtable<>();
    private static final PrototypeManager pm = new PrototypeManager();

    //初始化HashTable
    private PrototypeManager(){
        ht.put("far", new FAR());
        ht.put("srs", new SRS());
    }

    //为HashTable增加新的原型对象
    public void addOfficialDocument(String key, OfficialDocument doc){
        ht.put(key, doc);
    }

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

    public static PrototypeManager getPrototypeManager(){
        return pm;
    }

}

客户端:Client

public class Client {
    public static void main(String[] args) {
        PrototypeManager pm = PrototypeManager.getPrototypeManager();

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

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

运行结果如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值