设计模式之——原型模式

前言

在 Java 中,我们可以使用 new 关键字指定类名来生成类的实例。但是,有的时候,我们也会在不指定类名的前提下生成实例,例如像图形编辑器中拖动现有的模型工具制作图形的实例,这种是非常典型的生成实例的过程太过复杂,很难根据类来生成实例场景,因此需要根据现有的实例来生成新的实例。
像这样根据实例来生成新的实例的模式,我们称之为 原型模式
在软件开发过程中,我们经常会遇到需要创建多个相同或者相似对象的情况,因此 原型模式 的使用频率还是很高的。

定义

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

UML图

在原型模式中涉及 PrototypeConcretePrototypeClient 三个角色。

prototype.png

代码如下:

/**
 * @author zhangguoji
 * 2017/8/4 21:46
 */
interface Prototype {
    Prototype clone();
}

class ConcretePrototype implements Prototype {

    private String attr; // 成员属性

    /**
     * 克隆方法
     */
    @Override
    public ConcretePrototype clone() {
        // 创建新对象
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.setAttr(this.attr);
        return prototype;
    }

    @Override
    public String toString() {
        return "ConcretePrototype[attr=" + attr + "]";
    }

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

    public String getAttr() {
        return this.attr;
    }
}

public class Client {
    public static void main(String[] args) {
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.setAttr("Kevin Zhang");

        ConcretePrototype prototype2 = prototype.clone();

        System.out.println(prototype.toString());
        System.out.println(prototype2.toString());
    }
}

输出结果:

ConcretePrototype[attr=Kevin Zhang]
ConcretePrototype[attr=Kevin Zhang]

Process finished with exit code 0

Java的原型模式

Java 中使用原型模式很简单, 它为我们提供了复制实例的 clone() 方法。

实际上,所有的 Java 类都继承自 java.lang.Object。Object 类提供一个 clone() 方法,因此,在 Java 中可以直接使用 Object 提供的 clone() 方法来实现对象的克隆。

值得注意的是,被复制对象的类必须实现 java.lang.Cloneable 接口,如果没有实现 java.lang.Cloneable 接口的实例调用了 clone() 方法,会在运行时抛出CloneNotSupportedException 异常。

Cloneable 是一个标记接口,在 Cloneable 接口中并没有声明任何方法,它只是被用来标记可以使用 clone() 方法进行复制。

public interface Cloneable {
}

案例分析

现在,我有一个消息系统的需求,希望复制原先消息模板进行快速创建,然后可以进行修改后保存。

下面,我们通过 Java 的 clone()方法来实现对象的克隆。

Message 就是具体原型类,复制实现 clone() 方法。
Message代码:

/**
 * @author zhangguoji
 * @date 2017/8/4 21:56
 */
class Message implements Cloneable{
    private String author;
    private String content;

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getContent() {
        return content;
    }

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

    @Override
    protected Object clone() {
        Message message = null;
        try {
            message = (Message) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return message;
    }

    @Override
    public String toString() {
        return "Message{" +
                "author='" + author + '\'' +
                ", content='" + content + '\'' +
                '}';
    }
}

Client代码:


/**
 * @author zhangguoji
 * @date 2017/8/4 22:00
 */
public class Client {
    public static void main(String[] args) {
        Message message = new Message();
        message.setAuthor("ZGJ");
        message.setContent("2017-08-04【消息】");

        Message message1 = (Message) message.clone();
        message.setContent("2017-08-05【消息】");

        System.out.println(message);
        System.out.println(message1);
    }
}

结果:

Message{author='ZGJ', content='2017-08-05【消息】'}
Message{author='ZGJ', content='2017-08-04【消息】'}

思考

既然要创建新的实例,为什么不直接使用 new XXX(),而要设计出一个原型模式进行实例的复制呢?

有的时候,我们也会在不指定类名的前提下生成实例,例如像图形编辑器中拖动现有的模型工具制作图形的实例,这种是非常典型的生成实例的过程太过复杂,很难根据类来生成实例场景,因此需要根据现有的实例来生成新的实例。
Prototype原型模式是一种创建型设计模式,它主要面对的问题是:“某些结构复杂的对象”的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是他们却拥有比较稳定一致的接口。

还比如,类初始化需要消化非常多的资源,我们也可以考虑使用原型模式。因为,原型模式是在内存进行二进制流的拷贝,要比直接 new 一个对象性能好很多。

克隆的对象是全新的吗?

原型模式通过 clone() 方法创建的对象是全新的对象,它在内存中拥有新的地址,通过==符号判断返回是flase。

深克隆和浅克隆

clone() 方法使用的是浅克隆。浅克隆对于要克隆的对象, 会复制其基本数据类型和 String 类型或其他final类型的属性的值给新的对象. 而对于非基本数据类型的属性,例如数组、集合, 仅仅复制一份引用给新产生的对象, 即新产生的对象和原始对象中的非基本数据类型的属性都指向的是同一个对象。

此外,还存在深克隆。深克隆对于要克隆的对象, 对于非基本数据类型的属性,例如数组、集合支持复制。换句话说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。

浅克隆和深克隆的主要区别在于,是否支持引用类型的成员变量的复制。

在 Java 中,如果需要实现深克隆,可以通过序列化等方式来实现。
我们在消息中增加一个附件类


import java.io.Serializable;

/**
 * @author zhangguoji
 * @date 2017/8/4 22:10
 */
public class Attachment implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name;

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Attachment{" +
                "name='" + name + '\'' +
                '}';
    }
}

在消息中加入这个附件类


import java.io.*;

/**
 * @author zhangguoji
 * 2017/8/4 21:56
 */
class Message implements Serializable {
    private static final long serialVersionUID = 1L;
    private String author;
    private String content;
    private Attachment attachment;

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getContent() {
        return content;
    }

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

    public Attachment getAttachment() {
        return attachment;
    }

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

    public Message deepClone() throws IOException, ClassNotFoundException {
        /**
         * 将对象序列化到对象数组流中
         */
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);

        /**
         * 从流中读取对象
         */
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (Message) ois.readObject();
    }
}

使用序列化实现深度克隆,然后再客户端中测试是否深度克隆成功


import java.io.IOException;

/**
 * @author zhangguoji
 * @date 2017/8/4 22:30
 */
public class Client {
    public static void main(String[] args) {
        Message message = new Message();
        message.setAuthor("ZGJ");
        message.setContent("2017-08-04【消息】");
        Attachment attachment = new Attachment();
        attachment.setName("附件");
        message.setAttachment(attachment);
        Message message1 = null;
        try {
            message1 = message.deepClone();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("克隆失败");
        }
        System.out.println("消息是否相同:" + (message == message1));
        System.out.println("附件是否相同:" + (message.getAttachment() == message1.getAttachment()));
    }
}

结果如下:

消息是否相同:false
附件是否相同:false

注意

一般而言,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接口的类相当于具体原型类。

总结

原型模式的目的在于,根据实例来生成新的实例,我们可以很方便的快速的创建实例。

在 Java 中使用原型模式很简单, 被复制对象的类必须实现 java.lang.Cloneable 接口,并重写 clone() 方法。

使用原型模式的时候,尤其需要注意浅克隆和深克隆问题。在 Java 中,如果需要实现深克隆,可以通过序列化等方式来实现

个人博客: http://blog.zgj12138.cn
CSDN: http://blog.csdn.net/zgj12138

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

推荐阅读更多精彩内容