Java设计模式-创建型模式-原型模式

此系列文章为清华大学出版社出版刘伟编著《Java设计模式》的学习笔记。

>>全部23种设计模式<<

1 概述

原型模式是一种对象创建型模式,它的工作原理很简单:将一个原型对象传给要发动创建的对象(即客户端对象),这个发动创建的对象通过请求原型对象复制自己来实现创建过程。

2 结构与实现

2.1 原型模式结构

原型模式包含 3 个角色

  1. AbstractPrototype (抽象原型类):它是声明克隆方法的接口,是所有具体原型类的公共父类,它可以是抽象类也可以是接口,甚至还可以是具体实现类。
  2. ConcretePrototype (具体圆形类):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回一个自己的克隆对象。
  3. Client (客户类):在客户类中,让一个原型对象克隆自身从而创建一个新的对象,只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。由于客户类针对抽象原型类编程,因此用户可以根据需要选择具体的原型类,系统具有较好的可扩展性,增加或更换具体的原型类都很方便。

2.2 浅克隆与深克隆

根据在复制原型对象的同时,是否复制包含在原型对象中的引用对象成员,原型模式的克隆机制分为浅克隆深克隆

2.2.1 浅克隆

浅克隆与深克隆的讨论针对的是原型类中的引用对象成员,而浅克隆的特点是不会在内存中新建一个引用对象成员,与原型类共用该引用对象成员,在原型类执行浅克隆时,向克隆产生的新对象传递的是原型对象的引用对象的地址,造成两个类共享该引用成员变量。

2.2.2 深克隆

与浅克隆相反,深克隆在内存中重新申请空间,克隆一个不和原型类共享的引用成员变量。

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

Java 中,java.lang.Object 类提供了一个 clone() 方法,可以将一个Java对象复制一份。因此在 Java 中可以直接使用该 clone() 方法来实现对象的浅克隆

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

Java 中的 clone() 方法满足以下几点:

  1. 对任何对象 x,都有 x.clone() != x ,即克隆对象与原型对象不是同一个对象。
  2. 对任何对象 x,都有 x.clone().getClass() == x.getClass() ,即克隆对象与原型对象的类型一样。
  3. 如果对象 x 的 equals() 方法定义恰当,那么 x.clone().equals(x) 应该成立。

利用 Object 类的 clone() 方法,需要以下几个步骤:

  1. 在子类中覆盖父类的 clone() 方法,并声明为 public。
  2. 在子类的 clone() 方法中调用 super.clone()。
  3. 子类需要实现 Cloneable 接口。

此时,Object 类相当于抽象原型类,所有实现了 Cloneable 接口的类相当于原型类。

2.2.4 Java 中的 Serializable 接口

实现深克隆的方式有很多,当我们利用字节流来实现深克隆时,原型类需要实现 Serializable 接口。

2.3 原型模式举例

一、背景介绍

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

二、项目结构

项目结构.png

三、抽象原型类

package CreationalPattern.PrototypePattern.AbstractPrototype;

import CreationalPattern.PrototypePattern.Attachment;
import java.io.IOException;
import java.io.Serializable;

public abstract class WeeklyLog implements Cloneable, Serializable {
    protected Attachment attachment;
    protected String name;
    protected String date;
    protected String content;

    public abstract Attachment getAttachment();

    public abstract void setAttachment(Attachment attachment);

    public abstract String getName();

    public abstract void setName(String name);

    public abstract String getDate();

    public abstract void setDate(String date);

    public abstract String getContent();

    public abstract void setContent(String content);

    //todo:shadowClone()方法实现浅拷贝
    public abstract WeeklyLog shadowClone();

    //todo:deepClone()方法实现深拷贝
    public abstract WeeklyLog deepClone() throws IOException, ClassNotFoundException;
}

四、具体原型类

package CreationalPattern.PrototypePattern.ConcretePrototype;

import CreationalPattern.PrototypePattern.AbstractPrototype.WeeklyLog;
import CreationalPattern.PrototypePattern.Attachment;
import java.io.*;

public class MyWeeklyLog extends WeeklyLog {
    protected Attachment attachment;
    protected String name;
    protected String date;
    protected String content;

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

    //todo:shadowClone()方法实现浅拷贝
    public CreationalPattern.PrototypePattern.AbstractPrototype.WeeklyLog shadowClone() {
        Object obj = null;
        try{
            obj = super.clone();
            return (CreationalPattern.PrototypePattern.AbstractPrototype.WeeklyLog)obj;
        } catch (CloneNotSupportedException e) {
            System.out.println("不支持复制");
            return null;
        }
    }

    //todo:deepClone()方法实现深拷贝
    public WeeklyLog deepClone() throws IOException, ClassNotFoundException {
        //todo:将对象写入流中
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(this);
        //todo:将对象从流中读出
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        return (WeeklyLog)ois.readObject();
    }
}

五、附件类

package CreationalPattern.PrototypePattern;

import java.io.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+" ] ");
    }
}

六、客户类

package CreationalPattern.PrototypePattern;

import CreationalPattern.PrototypePattern.AbstractPrototype.WeeklyLog;
import CreationalPattern.PrototypePattern.ConcretePrototype.MyWeeklyLog;
import java.io.IOException;

public class Client {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //todo:浅拷贝
        WeeklyLog preLog1,newLog1;
        preLog1 = new MyWeeklyLog();
        Attachment attachment1 = new Attachment();
        preLog1.setAttachment(attachment1);
        newLog1 = preLog1.shadowClone();
        System.out.println("WeeklyLog是否相同?"+(preLog1 == newLog1));//false
        System.out.println("Attachment是否相同?"+(preLog1.getAttachment() == newLog1.getAttachment()));//true
        //todo:深拷贝
        WeeklyLog preLog2,newLog2;
        preLog2 = new MyWeeklyLog();
        Attachment attachment2 = new Attachment();
        preLog2.setAttachment(attachment2);
        newLog2 = preLog2.deepClone();
        System.out.println("WeeklyLog是否相同?"+(preLog2 == newLog2));//false
        System.out.println("Attachment是否相同?"+(preLog2.getAttachment()==newLog2.getAttachment()));//false
    }
}

七、测试结果

测试结果.png

3 总结

3.1 原型模式优点

  1. 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新势力的创建效率。
  2. 扩展性较好,由于在原型模式中提高了创建原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统没有任何影响。
  3. 原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式不需要,原型模式中产品的复制是通过封装在原型内部的克隆方法实现的,无需专门的工厂类。
  4. 可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(例如恢复到某一历史状态),可辅助实现撤销操作。

3.2 原型模式缺点

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

3.3 原型模式适用环境

  1. 创建对象成本较大(例如初始化需要占用较长的时间、占用太多的CPU资源或者网络资源),新对象可以通过复制已有对象来获得,如果是相似对象,则可以对其成员变量稍作修改。
  2. 系统要保存对象的状态,而对象的状态变化很小。
  3. 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。

>>全部23种设计模式<<

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容