设计模式-原型

原型模式定义:
原型模式(prototype pattern)是指原型实例指定创建对象的种类,并通过克隆这些原型创建新的对象,不调用构造函数,属于创建型模式。

示例1: 分析浅克隆带来的问题
示例2: 序列化深度克隆
示例3: 还原克隆破坏单例的车祸现场

使用场景:
1.类初始化消耗资源较多
2.new产生的一个对象需要非常繁琐的过程(数据准备,访问权限等)
3.构造函数比较复杂
4.循环体中生产大量对象时

优点:
1.jdk自带的原型模型是基于内存二进制流拷贝,相比直接new对象性能有所提升。
2.可以使用深克隆方式保存对象的状态,不用频繁使用构造函数或get set设置参数,简化了创建过程。
缺点:
1.必须配备一个克隆方法。
2.当对已有类进行改造的时候需要修改代码,违反开闭原则。

示例1:

/**
*   浅克隆
 * 创建具体原型类,实现jdk自有的克隆接口
 * 以下分 ①,② 线
 */
@Data
public class ConcretePrototype implements Cloneable{
    // ① 基础基本数据类型
    private Integer age;
    private String name;
    // ② 后加入引用型
    private List<String> ella;
    /**
     * 重写父类的克隆方法,省去手动get set
     * @return
     */
    @Override
    public ConcretePrototype clone() {
        try {
            return (ConcretePrototype)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
    @Override
    public String toString() {
        return "ConcretePrototype{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", ella=" + ella +
                '}';
    }
}

public class MainExcute {
    private static final Logger logger = Logger.getLogger(MainExcute.class);
    public static void main(String[] args) {
        // 原型对象
        ConcretePrototype prototype = new ConcretePrototype();
        // ①
        prototype.setName("张三");
        prototype.setAge(20);
        // ② 后加入
        List<String> ella = new ArrayList();
        ella.add("吉他");
        prototype.setElla(ella);
        logger.info("原型对象: " + prototype);
        // 克隆对象
        ConcretePrototype clonePrototype = prototype.clone();
        // ② 注: 正常来说给克隆对象修改后应该于原型对象不一样了,因为原型对象并没有增加美术这门爱好
        clonePrototype.getElla().add("美术");
        logger.info("克隆对象: " + clonePrototype);
        // ② 问题: 但把原型对象输出后却发现原型竟然跟着克隆产生变化了
        logger.info("原型对象: " + prototype);
        // false 两个对象进行比较后又确实是不同的对象,但为什么输出结果是一样的? 要知道原型是并没有做修改的
        logger.info(prototype == clonePrototype);
        // true 继续比较两个对象的ella, 神奇发现ella值是相同的
        logger.info(prototype.getElla() == clonePrototype.getElla());
       /**
         * 得出结论: 原型跟克隆对象的内存地址不一样,但是他们共用了ella值的内存地址。
         * 揪出原因: age, name 属于基本跟特殊数据类型,而ella是属于引用类型,我们的clone方法最终只会把引用类型内存地址的值
         * 拷贝过去,本没有把具体元素做复制处理,所以当修改克隆对象时导致原型也产生变化,这样显然不符合我们的预期。
         * 
         * 解决: 实现深度克隆
         */
    }
}
输出结果: 
// ① 没有加入引用类型时克隆是正常的
原型对象: ConcretePrototype{age=20, name='张三'}
克隆对象: ConcretePrototype{age=20, name='张三'}
// ②  增加引用型后输出表面看似没有问题
原型对象: ConcretePrototype{age=20, name='张三', ella=[吉他]}
克隆对象: ConcretePrototype{age=20, name='张三', ella=[吉他]}
// ②  但是当克隆对象进行修改后原型竟然也跟着变化了
克隆对象: ConcretePrototype{age=20, name='张三', ella=[吉他, 美术]}
原型对象: ConcretePrototype{age=20, name='张三', ella=[吉他, 美术]}

示例2:

/**
 *  从例1 延申序列化深度克隆,需要实现序列化接口
 */
@Data
public class ConcretePrototype implements Cloneable,Serializable{
    private Integer age;
    private String name;
    private List<String> ella;
    /**
     * 为了遵循里氏替换原则,不改变父类方法本身具有的含义(默认浅克隆),
     * 因此定义新的方法,通过序列化与反序列化实现深克隆。
     */
    public ConcretePrototype deepClone() {
        try {
            // 将对象变成字节流
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            // 将字节流变成java能够识别的字节
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            // 反序列化得到我们的对象
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            return (ConcretePrototype)ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     *  默认浅克隆
     * 重写父类的克隆方法,省去手动get set
     * @return
     */
    @Override
    public ConcretePrototype clone() {
        try {
            return (ConcretePrototype)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
    @Override
    public String toString() {
        return "ConcretePrototype{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", ella=" + ella +
                '}';
    }
}

public class MainExcute {
    private static final Logger logger = Logger.getLogger(MainExcute.class);
    public static void main(String[] args) {
        // 原型对象
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.setName("张三");
        prototype.setAge(20);
        List<String> ella = new ArrayList();
        ella.add("吉他");
        prototype.setElla(ella);
        // * 换成调用深克隆函数,输出结果符合期望值
        ConcretePrototype clonePrototype = prototype.deepClone();
        clonePrototype.getElla().add("美术");
        logger.info("克隆对象: " + clonePrototype);
        logger.info("原型对象: " + prototype);
        // false
        logger.info(prototype == clonePrototype);
        // false
        logger.info(prototype.getElla() == clonePrototype.getElla());
    }
}
输出结果: 
克隆对象: ConcretePrototype{age=20, name='张三', ella=[吉他, 美术]}
原型对象: ConcretePrototype{age=20, name='张三', ella=[吉他]}

示例3:

/**
 *  假设原型本身就是一个单例,而原型的创建又不需要构造方法,
 *  那么单列的构造方法是不是私有的就跟原型没有半毛钱关系,因为原型不走构造方法,
 *  而单例是通过构造方法创建的,所以导致冲突
 */
@Data
public class ConcretePrototype implements Cloneable,Serializable{
    // 演示: 创建单例
    private static ConcretePrototype instance = new ConcretePrototype();
    private ConcretePrototype(){}
    public static ConcretePrototype getInstance(){
        return instance;
    }
    /**
     *  克隆方法根本不走构造函数,构造私有变得无意义
     * @return ConcretePrototype
     */
    @Override
    public ConcretePrototype clone() {
        try {
            return (ConcretePrototype)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

public class MainExcute {
    private static final Logger logger = Logger.getLogger(MainExcute.class);
    public static void main(String[] args) {
        ConcretePrototype prototype = ConcretePrototype.getInstance();
        ConcretePrototype clonePrototype = prototype.clone();
        // * false 克隆出来得对象竟然不一样,明显单例已经被破坏
        logger.info(prototype == clonePrototype);
        /**
         * 最终结果: 发现他们是互斥的,如果对象是单例,则不能是原型
         */
    }
}
image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容