设计模式(七)原型模式

在有些系统中,存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建对象, 会比较复杂且耗时耗资源,用原型模式生成对象就很高效,就像孙悟空拔下猴毛轻轻一吹就变出很多孙悟空一样简单。

模式的定义

原型(Prototype)模式的定义:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。

模式的结构与实现

结构

由于 Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式很简单。

原型模式包含以下主要角色:

  • 抽象原型类:规定了具体原型对象必须实现的接口。
  • 具体原型类:实现抽象原型类的clone() 方法,它是可被复制的对象。
  • 使用类:使用具体原型类中的 clone() 方法来复制新的对象。

实现

原型模式的克隆分为浅克隆和深克隆,Java 中的 Object 类提供了浅克隆的 clone() 方法,具体原型类只要实现 Cloneable 接口就可实现对象的浅克隆,这里的 Cloneable 接口就是抽象原型类。使用了克隆的方式产生新的对象,新生成的对象也可以 根据需要稍做修改以适应需求。

这个时候有同学就会问为什么 Object 里提供了 clone 方法,为什么还需要实现 Cloneable 接口。
Java语言虽然提供了这个方法,但考虑到安全问题,一方面将 clone() 方法的访问级别设置为 protected,以限制外部类访问。另一方面,强制需要提供 clone 功能的子类实现 java.lang.Cloneable 接口。在运行期,JVM 会检查调用 clone() 方法的类,如果该类未实现 java.lang. Cloneable 接口。则抛出 CloneNotSupportedException 异常。

public class Goods implements Cloneable{

    public String name;

    public Date date;

    public Goods(String name,Date date) {
        this.name = name;
        this.date = date;
    }

    @Override
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode())+ "=>>Goods{" +
                "name='" + name + '\'' +
                ", date=" + date +
                '}';
    }

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

    public static void main(String[] args) throws CloneNotSupportedException {
        Goods a = new Goods("面包",new Date());
        Goods clone = (Goods) a.clone();
        System.out.println(a);
        System.out.println(clone);
        System.out.println(a.date==clone.date);
        System.out.println(a==clone);
    }

}

上面代码简单实现了原型模式的克隆。我们可以看下运行结果:

com.yuxuan.learning.design.patterns.creative.prototype.Goods@3cd1f1c8=>>Goods{name='面包', date=Tue Aug 1 14:27:17 CST 2012}
com.yuxuan.learning.design.patterns.creative.prototype.Goods@6979e8cb=>>Goods{name='面包', date=Tue Aug 1 14:27:17 CST 2012}
goods==clone? false
goods.date==clone.date?true

由结果可见,生成后的对象和源对象在内存中的确是两个不同的地址,而且生成后的对象与源对象中的属性是完全一样的。

但是,仔细一看会发现一个问题,就是虽然新对象和源对象不是一样的对象,但是其中的属性对象和源对象的属性对象共享一个对象,比如上面的 date 属性。

浅克隆与深克隆

浅克隆与深克隆的区别:

  • 浅克隆:被复制的所有变量都具有与原来对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。
  • 深克隆:把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象。

很显然上面实现的是一个浅克隆,因为克隆出来的对象的 date 属性和源对象的 date 属性指向的还是同一个对象地址。

下面我们调整一下 clone 方法里的代码,具体如下:

public class Goods implements Cloneable{

    public String name;

    public Date date;

    public Goods(String name,Date date) {
        this.name = name;
        this.date = date;
    }

    @Override
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode())+ "=>>Goods{" +
                "name='" + name + '\'' +
                ", date=" + date +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Goods goods = (Goods)super.clone();
        goods.date = (Date) this.date.clone();
        return goods;
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        Goods goods = new Goods("面包",new Date());
        Goods clone = (Goods) goods.clone();
        System.out.println(goods);
        System.out.println(clone);
        System.out.println("goods==clone? "+(goods==clone));
        System.out.println("goods.date==clone.date?"+(goods.date==clone.date));

    }

}

我们在来看一下运行结果过:

com.yuxuan.learning.design.patterns.creative.prototype.Goods@3cd1f1c8=>>Goods{name='面包', date=Tue Aug 1 14:46:25 CST 2012}
com.yuxuan.learning.design.patterns.creative.prototype.Goods@6979e8cb=>>Goods{name='面包', date=Tue Aug 1 14:46:25 CST 2012}
goods==clone? false
goods.date==clone.date?false

从运行结果我们不难看出,现在 date 属性指向的不是同一个对象了。现在就是深克隆了。

需要注意的是,clone 方法只会进行复制,并不会调用被复制实例的构造函数。对于在生成实例时需要进行特殊的初始化处理的类,需要自己在 clone 方法中进行处理。

总结

原型模式为我们提供了另外一种高效创建对象的方法。使用原型模式,我们可以不了解原型对象的任何细节以及它内部 的层次的结构,不影响,但是必须要实现特定的接口。当需要的对象需要从现有的对象中复制时,通常适合使用原型模式。

原型模式和工厂模式的区别 :

  • 原型模式可以选择维护一个产品的原型对象,并在方法中返回原型对象的克隆。
  • 工厂模式直接返回新的产品对象,此对象是根据类中的代码新创建的。

原型模式也有它的缺点。每一个原型的子类都必须实现 Clone 操作,这可能会很困难。 例如,当所使用此模式的类已经存在时就难以新增 Clone 操作。当内部包括一些不支持拷贝或由循环引用的对象时,实现克隆可能也会很困难。

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

推荐阅读更多精彩内容