设计模式之原型模式(Prototype)

设计模式中有六大原则和二十三设计模式。
其中六大原则分别为:单一职责原则、开闭原则、里氏替换原则、依赖倒置原则、接口隔离原则、迪米特原则。
二十三设计模式:单例模式、Builder 模式、原型模式、工厂方法模式、抽象工厂模式、策略模式、状态模式、责任链模式、解释器模式、命令模式、观察者模式、备忘录模式、迭代器模式、模版方法模式、访问者模式、中介模式、代理模式、组合模式、适配器模式、装饰模式、享元模式、外观模式、桥接模式。

定义

  • 用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。《Android 源码设计模式解析与实践》
  • 原型模式是一种创建型设计模式,Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。[1]

使用场景

  • 类初始化需要消耗很多资源(数据、硬件资源等)。
  • 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限。
  • 一个对象需提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象共调用者使用,即保护性拷贝。

说到拷贝,我们再说下深拷贝和浅拷贝。

深拷贝和浅拷贝

  • 浅拷贝
    创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
    常用的方法有:一一赋值、clone()等。

需要注意的是:对于我们常用【=】它不属于浅拷贝,属于赋值,是同一个对象。如果是基本类型我们拷贝的是它的值;如果是对象则是拷贝的是对象内存地址的引用,其实他们还是同一个对象。

  • 深拷贝
    创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
    常用的方法有:层层拷贝(每个元素都需要实现clone()方法)、序列化反序列化、Gson 转换、构造函数(相当重新new一个对象,一一赋值)等

示例

  • 对象类
/**
 * 元素类
 */
public class Children implements Cloneable {
    private String content;

    @Override
    protected Children clone() throws CloneNotSupportedException {
        return (Children)super.clone();
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}
/**
 * 原型类
 */
public class Prototype implements Cloneable {

    private String message;
    private String title;
    private Children children;
    // ...

    @Override
    protected Prototype clone() throws CloneNotSupportedException {
        // 浅拷贝
        return (Prototype)super.clone();
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Children getChildren() {
        return children;
    }

    public void setChildren(Children children) {
        this.children = children;
    }
}
  • 实现
    public static void main(String[] args) throws CloneNotSupportedException {
        Prototype prototype1 = new Prototype();
        prototype1.setTitle("Title1");
        prototype1.setMessage("Message1");
        Children children = new Children();
        children.setContent("Content1");
        prototype1.setChildren(children);
        System.out.println("prototype1------\n    hashCode = "+prototype1.hashCode()+"\n    title ="+prototype1.getTitle()+"\n    message="+prototype1.getMessage());
        System.out.println("Children1-------\n    hashCode = "+prototype1.getChildren().hashCode()+"\n    content="+prototype1.getChildren().getContent());
        Prototype prototype2 = prototype1.clone();
        System.out.println("prototype2------\n    hashCode = "+prototype2.hashCode()+"\n    title ="+prototype2.getTitle()+"\n    message="+prototype2.getMessage());
        System.out.println("Children2-------\n    hashCode = "+prototype2.getChildren().hashCode()+"\n    content="+prototype2.getChildren().getContent());
        prototype2.getChildren().setContent("Content2");
        System.out.println("prototype11------\n    hashCode = "+prototype1.hashCode()+"\n    title ="+prototype1.getTitle()+"\n   message="+prototype1.getMessage());
        System.out.println("Children11-------\n    hashCode = "+prototype1.getChildren().hashCode()+"\n    content="+prototype1.getChildren().getContent());
        System.out.println("prototype22------\n    hashCode = "+prototype2.hashCode()+"\n    title ="+prototype2.getTitle()+"\n   message="+prototype2.getMessage());
        System.out.println("Children22-------\n    hashCode = "+prototype2.getChildren().hashCode()+"\n    content="+prototype2.getChildren().getContent());
    }
  • 结果
prototype1------
    hashCode = 531885035
    title =Title1
    message=Message1
Children1-------
    hashCode = 1418481495
    content=Content1
prototype2------
    hashCode = 303563356
    title =Title1
    message=Message1
Children2-------
    hashCode = 1418481495
    content=Content1
prototype11------
    hashCode = 531885035
    title =Title1
   message=Message1
Children11-------
    hashCode = 1418481495
    content=Content2
prototype22------
    hashCode = 303563356
    title =Title1
   message=Message1
Children22-------
    hashCode = 1418481495
    content=Content2

从上边的例子我们可以看出 Prototype 的 clone() 方法只是创建它自己本身新对象,而它的元素 Children 还是指向了原有的地址,这就是浅拷贝。要是想实现深拷贝,只需要重写 Prototype 的 clone() 方法。

    @Override
    protected Prototype2 clone() throws CloneNotSupportedException {
        // 浅拷贝
        Prototype2 prototype2 = (Prototype2)super.clone();
        prototype2.children = this.children.clone();
        return prototype2;
    }
prototype1------
    hashCode = 531885035
    title =Title1
    message=Message1
Children1-------
    hashCode = 1418481495
    content=Content1
prototype2------
    hashCode = 303563356
    title =Title1
    message=Message1
Children2-------
    hashCode = 135721597
    content=Content1
prototype11------
    hashCode = 531885035
    title =Title1
   message=Message1
Children11-------
    hashCode = 1418481495
    content=Content1
prototype22------
    hashCode = 303563356
    title =Title1
   message=Message1
Children22-------
    hashCode = 135721597
    content=Content2

以上对原型模式(深拷贝、浅拷贝)进行了举例。接下来我们再说下它的优缺点。

优缺点

优点

  • 性能高:原型模式是在内存中二进制流的拷贝,要比直接new 一个对象性能好,特别是在循环体中产生大量对象时,
  • 简化流程:可以利用深拷贝,实现状态记录、回退等功能。

缺点

  • 构造函数不会执行:原型模式是在内存中进行拷贝的,会跳过构造函数。
  • 实现繁琐:必须实现clone()方法
  • 拷贝风险:浅拷贝和深拷贝,一旦使用不正确,就会造成大灾难。

总结

浅拷贝和深拷贝有多种实现方法,这里只用一种方法举例,其他的方法可以参照深拷贝浅拷贝
原型模式的宗旨就是对象的拷贝,节省创建多次创建新对象带来的消耗,提高性能。但是也需要注意深浅拷贝带来的问题。

DEMO

参考

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

推荐阅读更多精彩内容