【设计模式(四)】原型模式

个人学习笔记分享,当前能力有限,请勿贬低,菜鸟互学,大佬绕道

如有勘误,欢迎指出和讨论,本文后期也会进行修正和补充


前言

原型模式是一种创建型设计模式, 使你能够复制已有对象, 而又无需使代码依赖它们所属的类。

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

当直接创建对象的代价比较大时,则采用这种模式。


1.介绍

使用目的:已知原型实例的情况下,可以获得相同的实例对象

使用时机:需要动态的生成和删除实例模型

解决问题:动态的创建和删除实例

实现方法:实现Cloneable类的clone()方法

使用场景

  • 通过new一个对象需要极其繁琐的数据准备或者权限,那么推荐使用原型模式
  • 对象初始化需要消耗大量资源的时候,从旧的对象进行克隆出新对象,即可不必重复初始化
  • 一个对象可能有多个修改者,那么可以克隆出去多份新对象供其使用

应用实例:

  • 细胞分裂
  • JAVA 中的 Object clone() 方法

优点

  • 性能提高,构建新的对象只需要拷贝旧的对象即可
  • 逃避构造函数的约束,根本不经过构造函数

缺点

  • 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候
  • 类必须实现 Cloneable接口。

注意事项:既然是拷贝,那么必须有源对象才能实现,否则还是得构建一个全新的对象


分类:通过拷贝的方法和其内容,分为浅拷贝和深拷贝

  • 浅拷贝:只拷贝源对象的基本数据,而不拷贝容器,引用等等,一般实现Cloneable类并重写clone()方法
  • 深拷贝:拷贝源对象的一切,包括数据、容器、引用等等,一般通过实现 Serializable读取二进制流,直接复制出新对象,也可通过其他方式实现


2.实现方案

深拷贝或者浅拷贝是结果,而非简单的由方案决定,比如在clone方法中拷贝全部内容,也可以达到深拷贝的效果

请注意,示例中修正新对象仅为了测试,实际应用中请视情况处理,理论上应当保持新旧对象尽可能一致

2.1.方案1:实现Cloneable类

实现Cloneable类并重写clone()方法即可,在该方法中设定好需要拷贝的内容,调用源对象中的该方法即可获得新的对象

步骤

  1. 定义一个实现了Cloneable接口的抽象类,并实现clone()方法

    abstract class Animal implements Cloneable {
        protected String type;
        protected List<String> typeSet = new ArrayList<>();
    
        public Animal(String type) {
            this.type = type;
            typeSet.add(type);
        }
    
        void say() {
            System.out.println("myType is " + type);
            System.out.println("myTypeSet is " + String.join(",", typeSet));
        }
    
        public Animal clone() {
            Animal clone = null;
            try {
                //克隆对象
                clone = (Animal) super.clone();
                //克隆对象里的复杂对象,若不拷贝则会使用源对象里的复杂对象
                //clone.typeSet = (List<String>) ((ArrayList) this.typeSet).clone();
    
                //修正新对象
                clone.type = type + "Cloned";
                clone.typeSet.add(clone.type);
    
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return clone;
        }
    }
    

    这里我们在克隆时,会对新对象进行修正,而其余内容保持与源对象一致

    复杂对象后面进行测试

  2. 定义实体类,实现抽象类,简单易懂

    class Dog extends Animal {
    
        public Dog() {
            super("dog");
        }
    
        @Override
        void say() {
            super.say();
            System.out.println("汪!");
        }
    }
    
    class Cat extends Animal {
    
        public Cat() {
            super("cat");
        }
    
        @Override
        void say() {
            super.say();
            System.out.println("喵!");
        }
    }
    
  3. 定义数据源初始化和调用方法

    private Map<String, Animal> animalMap = new HashMap<>();
    
    public void initAnimal() {
        Dog dog = new Dog();
        animalMap.put("dog", dog);
        Cat cat = new Cat();
        animalMap.put("cat", cat);
    }
    
    public Animal getAnimal(String type) {
        if (animalMap.containsKey(type)) {
            return animalMap.get(type).clone();
        } else {
            return null;
        }
    }
    

    initAnimal()方法中初始化出两个对象,并将其存储入map

    getAnimal()方法中取出对象的克隆

  4. 测试调用

        public static void main(String[] args) {
            //初始化
            initAnimal();
    
            //第一轮测试
            System.out.println("test turn 1:");
            Animal dog1 = getAnimal("dog");
            Animal cat1 = getAnimal("cat");
            dog1.say();
            cat1.say();
    
            //第二轮测试
            System.out.println("test turn 2:");
            Animal dog2 = dog1.clone();
            dog2.say();
    
            //第三轮测试
            System.out.println("test turn 3:");
            dog1.say();
            System.out.println("clone equals:" + Objects.equals(dog1.typeSet, dog2.typeSet));
        }
    


完整代码

package com.company.clone;

import java.util.*;

class Animal implements Cloneable {
    protected String type;
    protected List<String> typeSet = new ArrayList<>();

    public Animal(String type) {
        this.type = type;
        typeSet.add(type);
    }

    void say() {
        System.out.println("myType is " + type);
        System.out.println("myTypeSet is " + String.join(",", typeSet));
    }

    public Animal clone() {
        Animal clone = null;
        try {
            //克隆对象
            clone = (Animal) super.clone();
            //克隆对象里的复杂对象,若不拷贝则会使用源对象里的复杂对象
            //clone.typeSet = (List<String>) ((ArrayList) this.typeSet).clone();

            //修正新对象
            clone.type = type + "Cloned";
            clone.typeSet.add(clone.type);

        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return clone;
    }
}

class Dog extends Animal {

    public Dog() {
        super("dog");
    }

    @Override
    void say() {
        super.say();
        System.out.println("汪!");
    }
}

class Cat extends Animal {

    public Cat() {
        super("cat");
    }

    @Override
    void say() {
        super.say();
        System.out.println("喵!");
    }
}

public class CloneTest {
    public static void main(String[] args) {
        //初始化
        initAnimal();

        //第一轮测试
        System.out.println("test turn 1:");
        Animal dog1 = getAnimal("dog");
        Animal cat1 = getAnimal("cat");
        dog1.say();
        cat1.say();

        //第二轮测试
        System.out.println("test turn 2:");
        Animal dog2 = dog1.clone();
        dog2.say();

        //第三轮测试
        System.out.println("test turn 3:");
        dog1.say();
        System.out.println("clone equals:" + Objects.equals(dog1.typeSet, dog2.typeSet));
    }

    private static Map<String, Animal> animalMap = new HashMap<>();

    public static void initAnimal() {
        Dog dog = new Dog();
        animalMap.put("dog", dog);
        Cat cat = new Cat();
        animalMap.put("cat", cat);
    }

    public static Animal getAnimal(String type) {
        if (animalMap.containsKey(type)) {
            return animalMap.get(type).clone();
        } else {
            return null;
        }
    }
}

运行结果

image-20201009114117630

分析

  • 第一轮测试为一次克隆的结果,type后追加ClonedtypeSet包括2个数据,分别是旧type和新type
  • 第二轮测试为二次克隆的结果,type后追加两个ClonedtypeSet包括3个数据
  • 第三轮仍为一次克隆的结果,type后只追加了一个Cloned,但typeSet却有3个数据,与二次克隆是同一个typeSet

也就是说克隆后,使用的typeSet是同一个

==这也就是所说的浅拷贝:只拷贝数据,而容器、引用等则直接使用源对象的,并不进行拷贝==

==如果需要深拷贝,则需要在clone()方法中对复杂对象进行复制==

如果启用clone()方法中注释掉的代码,运行结果会变成下面这样

image-20201009114814380

可以看到现在的typeSet不是同一个了,只是拷贝的时候复制了里面的内容


2.2.方案2:序列化后进行复制

即将对象以二进制完全复制一份新的,再转换为对象,通常使用Serializable和数据流一起实现

步骤

这里仅修改clone()方法,其余步骤与方案1一致

class Animal implements Serializable {
    protected String type;
    protected List<String> typeSet = new ArrayList<>();

    public Animal(String type) {
        this.type = type;
        typeSet.add(type);
    }

    void say() {
        System.out.println("myType is " + type);
        System.out.println("myTypeSet is " + String.join(",", typeSet));
    }

    public Animal clone() {
        Animal clone = null;
        try {
            //将对象写到流里
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            //从流里读回来
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            clone = (Animal) ois.readObject();
            //修正新对象
            clone.type = type + "Cloned";
            clone.typeSet.add(clone.type);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return clone;
    }
}

将对象写入流里,再读出来,并强制转换为对应类的对象即可

运行结果

image-20201009120155318

分析

可以看到结果是深拷贝,因为新对象时由数据流转换来的,复杂对象保留的是数据,而非引用地址,那么自然会构造新的


3.后记

实例中我们预先初始化所有源对象,使用map存储,使用的时候取出对应对象的克隆体

其实这就是最常见的应用场景,我们可以快速获得对应的对象,不需要每次都初始化和进行构造,而对深拷贝对象的修改都不会影响源对象,也就可以保证每次的源对象是相同且纯净的

一次初始化,之后便可快速得到新的对象,我们也可以在初始化的时候进行数据预设等等,视业务情况处理

总之,还是很实用的对吧~



作者:Echo_Ye

WX:Echo_YeZ

EMAIL :echo_yezi@qq.com

个人站点:在搭了在搭了。。。(右键 - 新建文件夹)

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