设计模式-原型模式

原文地址:LoveDev

原型模式是创建型模式的一种,其特点在于通过“复制”一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是我们所称的“原型”,这个原型是可定制的

原型模式
原型模式

使用场景:

  • 通过 new 产生一个对象需要非常繁琐的数据准备和访问权限
  • 一个对象需要提供给其他对象访问,每个调用者都有可能修改其属性,可以原型模式拷贝多个对象供调用者使用,即保护性拷贝
    uml 看起来是不是特别简单,用起来其实也很简单,Object 类已经提供了一个 clone() 方法,查看源码可以得知,想要使用该方法,还要实现一个标识接口Cloneableclone() 源码:
    protected Object clone() throws CloneNotSupportedException {
        if (!(this instanceof Cloneable)) {
            throw new CloneNotSupportedException("Class " + getClass().getName() +
                                                 " doesn't implement Cloneable");
        }

        return internalClone();
    }

Java语言提供的Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,这种空接口也称为标识接口,标识接口中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等

再来看看超级简单的示例代码:

具体原型类:

public class ConcretePrototype implements Cloneable {

    private String name;

    public String getName() {
        return name;
    }

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    
    @Override
    public String toString() {
        return "ConcretePrototype{" +
                "name='" + name + '\'' +
                ", person=" + person +
                '}';
    }
}

看着有没有特别的简单方便,但是这里面有一个坑在,这就牵扯到浅拷贝和深拷贝,上述的原型模式就是浅拷贝,也称为影子拷贝,如果需要拷贝的类中全部都是基础类型的属性,浅拷贝也是没有问题的,但是有引用类型的属性,就会出现问题了,出现问题的示例代码:

具体原型类:

public class Person {

    private String name;
    private String age;

    public String getName() {
        return name;
    }

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

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

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

// 具体原型类
public class ConcretePrototype implements Cloneable {

    private String name;
    private Person person;

    public String getName() {
        return name;
    }

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

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

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

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

Client类:

public class PrototypeActivity extends Activity {

    @BindView(R.id.prototype_text)
    TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_prototype);
        ButterKnife.bind(this);
    }

    @OnClick(R.id.prototype)
    public void prototype_click() {
        ConcretePrototype concretePrototype = new ConcretePrototype();
        concretePrototype.setName("Kevin");
        Person person = new Person();
        person.setName("Person");
        person.setAge("10");
        concretePrototype.setPerson(person);
        StringBuilder stringBuilder = new StringBuilder("concretePrototype:" + concretePrototype.toString());

        try {
            ConcretePrototype clone = (ConcretePrototype) concretePrototype.clone();
            clone.setName("LoveDev");
            stringBuilder.append("\nclone: ").append(clone.toString());     //第一次修改,正常

            Person newPerson = clone.getPerson();
            newPerson.setName("newPerson");
            newPerson.setAge("20");
            clone.setPerson(newPerson);
            stringBuilder.append("\n\n\nconcretePrototype: ").append(concretePrototype.toString());
            stringBuilder.append("\nclone: ").append(clone.toString());    //第二次修改,被拷贝对象同时被修改 
            
            stringBuilder.append("\n\n\n 对比两个类中的 Person 字段是否相同:").append(concretePrototype.getPerson() == clone.getPerson());

            mTextView.setText(stringBuilder);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

第一次修改的时候,只改了具体原型类中的String类型,打印结果没有是问题的,只修改了克隆后的类,第二次修改Person字段,再次打印的时候,发现原型类已经被修改了,导致这个问题的原因是因为浅拷贝只是拷贝了引用地址,两个对象指定的是同一内存地址,从打印结果就可以看的出来,要解决这个问题就要采用深拷贝进行克隆,在克隆对象时,对于引用类型的字段也要采用克隆的形式,修改后的示例代码:

public class ConcretePrototype implements Cloneable {

    private String name;
    private Person person;

    public String getName() {
        return name;
    }

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

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        ConcretePrototype concretePrototype = new ConcretePrototype();
        concretePrototype.name = this.name;
        concretePrototype.person = (Person) this.person.clone();
        return concretePrototype;
    }

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

再次执行,可以发现问题得以解决,原型模式一个很重要的用途就是保护性拷贝,再给其他模块提供接口访问一个不可修改的对象时,为了防止该模块负责人出于某种原因而修改该对象时,就要用原型模式进行保护性拷贝

优点:

看了很多的文章都说原型模式效率搞,下面分别是原型模式和直接new在10000次for循环中创建对象的内存消耗和耗时:

直接new对象,执行耗时为23毫秒,内存占用增加909K,对比图:


new对象对比
new对象对比

原型模式,执行耗时为44毫秒,内存占用增加876K,对比图:


原型模式对比
原型模式对比

从这些数据来看,原型模式增加了将近一倍的耗时,内存占用并没有少很多,这还是建立在不太准确的测试数据上,个人认为原型模式在效率方面比直接 new 对象并没有提高,不过还有其他的优点在:

  • 原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品
  • 可以利用深拷贝实现保护性拷贝,可实现撤销操作

缺点:

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

推荐阅读更多精彩内容