原型模式:如何快速的克隆出一个对象?

概念

原型模式是一种创建型设计模式,Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。

也就是说,当对象的创建过于复杂的时候,我们可以通过复制的方式创建新对象,这样可以大大的提升程序的效率。

什么时候使用原型模式

1.对象创建过于复杂:对象创建的时候需要经过计算、排序等操作。
2.对象时间过长:需要查询数据库或者通过rpc调用。
3.对象过多:比如一个集合中有一百万个对象,我现在需要修改这一百万的对象的数据,但是原数据不能动,所以这个时候使用原型模式比较适合。

具体的使用场景还是需要看项目情况而定,不能为了让代码看着高大上而去使用设计模式,比如就只有一个对象的创建(没有任何计算,没有调用数据库和rpc),这个时候如果使用原型模式,虽然效率也会有一点点的提升,但是这样做有点得不偿失,因为还要考虑到代码的可读性和可维护性多重条件。

这么说还是比较空洞,大家可能还是不知道在什么方面使用原型模式,接下来我们将以demo的形式来展示什么是原型模式。

没有使用原型模式的Demo

假设我java内存里面存放着博客的一些信息,点赞数、评论数、浏览数等等信息,现在我们有一个需求,我们需要每一个小时更新一次博客的信息,我们应该怎么做呢?

package com.ymy.test;


import com.ymy.entity.BlogStats;

import java.util.HashMap;
import java.util.Map;

public class PrototypeDesignPattern {

    /**
     * 内存中存放的数据
     * key:博客id
     * value:博客信息
     */
    private static Map<Integer, BlogStats> mapCache = new HashMap<Integer, BlogStats>();


    /**
     * 数据库存放的数据
     * key:博客id
     * value:博客信息
     */
    private static Map<Integer, BlogStats>  mapMysql = new HashMap<Integer, BlogStats>();

    /**
     * 最近一次的修改时间
     */
    private static Long updateTime = 1585882804208L;


    static {
        BlogStats blogStats = null;
        //初始化内存中java的博客信息
        for(int i = 0; i< 10 ;++i){
           blogStats = BlogStats.builder()
                   .id(i)
                   .title("java是世界上最好的语言"+i)
                   .likeNum(i)
                   .viewNum(i)
                   .commentNum(i)
                   .build();
           mapCache.put(i,blogStats);
        }

        //初始化数据库中java的博客信息
        for(int i = 0; i< 10 ;++i){
            blogStats = BlogStats.builder()
                    .id(i)
                    .title("java是世界上最好的语言"+i)
                    .likeNum(i+2)
                    .viewNum(i+2)
                    .commentNum(i+2)
                    .build();
            mapMysql.put(i,blogStats);
        }

    }

    /**
     * 每天定时更新
     */
    private static void update(){
        //我们需要将数据库存放的最新数据获取出来
        Map<Integer, BlogStats> newData = mapMysql;
        //将新获取到的数据替换掉内存中的数据
        mapCache = newData;
    }


    public static void main(String[] args) {
        System.out.println("内存中的原始数据:"+mapCache);
        //更新内存数据
        update();
        System.out.println("更新完之后内存中的数据:"+mapCache);
    }


}

package com.ymy.entity;

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
@Builder
public class BlogStats {

    /**
     * 博客id
     */
    private Integer id;

    /**
     * 博客标题
     */
    private String title;

    /**
     * 点赞数量
     */
    private Integer likeNum;

    /**
     * 浏览数量
     */
    private Integer viewNum;


    /**
     * 评论数
     */
    private Integer commentNum;

    /**
     * 更新的时间(时间戳)
     */
    private Long updateTime;


}

@Getter、@Setter、@ToString、@Builder需要引入依赖

<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

上面的代码很简单,就是内存中存放了一套数据,用于展示给用户看,不是实时的,按时间段更新,更新的方式就是在数据库中获取到最新的信息,然后替换掉内存中的数据,看似很简单,但是这种方式存在着很大的问题,那就是性能问题,为什么这么说,现在只有10篇博客,假设存在100w博客呢?我们也需要在数据库中获取出来,然后再替换掉内存中的数据?这样的做法是相当不推荐的,我们想想有没有什么比较好一点的方法呢?

我们内存中的数据并不是都需要修改的,100w数据真正需要修改的可能只有几百条或者几十条,如果从数据库全部取出替换,实在是没有必要,所以能不能,只取出变化的数据,然后做修改呢?答案是肯定的,我们一起来改造一下代码

package com.ymy.test;


import com.ymy.entity.BlogStats;

import java.util.HashMap;
import java.util.Map;

public class PrototypeDesignPattern {



    /**
     * 内存中存放的数据
     * key:博客id
     * value:博客信息
     */
    private static Map<Integer, BlogStats> mapCache = new HashMap<Integer, BlogStats>();


    /**
     * 数据库存放的数据
     * key:博客id
     * value:博客信息
     */
    private static Map<Integer, BlogStats>  mapMysql = new HashMap<Integer, BlogStats>();

    /**
     * 最近一次的修改时间
     */
    private static Long updateTime = 1585882804208L;


    static {
        BlogStats blogStats = null;
        //初始化内存中java的博客信息
        for(int i = 0; i< 10 ;++i){
           blogStats = BlogStats.builder()
                   .id(i)
                   .title("java是世界上最好的语言"+i)
                   .likeNum(i)
                   .viewNum(i)
                   .commentNum(i)
                   .status(1)
                   .build();
           mapCache.put(i,blogStats);
        }

        //初始化数据库中java的博客信息
        for(int i = 0; i<= 10 ;++i){
            blogStats = BlogStats.builder()
                    .id(i)
                    .title("java是世界上最好的语言"+i)
                    .likeNum(i+2)
                    .viewNum(i+2)
                    .commentNum(i+2)
                    .status(i >= 8 ? 0 : 1)
                    .build();
            mapMysql.put(i,blogStats);
        }
    }

    /**
     * 每天定时更新
     */
    private static void update(Map<Integer, BlogStats> changeData){
        for(Map.Entry<Integer, BlogStats> entry : changeData.entrySet()){
            Integer id = entry.getKey();
            BlogStats blogStats = entry.getValue();
            blogStats.setStatus(1);
            if(mapCache.containsKey(id)){
                mapCache.replace(id, blogStats);
            }else{
                mapCache.put(id,blogStats);
            }
        }
    }


    /**
     * 根据更新的状态获取到最新需要修改的数据
     * 由于这里仅仅只是模拟,所以这里我就随便造几条数据,你们主要看思想即可
     * @return
     */
    public static Map<Integer,BlogStats> getChangeData(){

        //在数据库中查询还没有更新过的数据
        //由于我是模拟,所以我直接伪造了几条数据充当需要修改的数据,还请见谅
        Map<Integer,BlogStats> changeDatas= new HashMap<Integer,BlogStats>();
        for(Map.Entry<Integer, BlogStats> entry : mapMysql.entrySet()){
            Integer id = entry.getKey();
            BlogStats blogStats = entry.getValue();
            if(blogStats.getStatus() == 0){
                changeDatas.put(id,blogStats);
            }
        }

        return changeDatas;
    }


    public static void main(String[] args) {
        print(mapCache);
        //获取修改的数据
        Map<Integer, BlogStats> changeData = getChangeData();
        //更新内存数据
        update(changeData);
         //将修改的博客状态改为1并写回数据库,这段省略
         
        System.out.println("更新过后=====================");
        print(mapCache);
    }


    private static void print( Map<Integer, BlogStats> map ){
        for(Map.Entry<Integer, BlogStats> entry : map.entrySet()){
            System.out.println("key:"+entry.getKey()+"  value:"+entry.getValue());
        }

    }
}

这是改造之后的代码,解释一下上面的代码,
第一:我不在往数据库中获取所有的博客数据,而是通过一个字段status表示这条记录是否更新过,只查询需要更新的博客信息即可。
第二:再修改的时候需要判断再数据库中查询出来的数据是否在内存中存在,如果存在,执行修改操作,如果不存在,执行添加操作。

按照上面的思路,我们的运行结果应该是博客id在0-7的信息无变化,博客id在8-9被修改,博客id等于10的被新增,那结果是不是这样呢?我们直接来看运行结果

key:0  value:BlogStats(id=0, title=java是世界上最好的语言0, likeNum=0, viewNum=0, commentNum=0, status=1)
key:1  value:BlogStats(id=1, title=java是世界上最好的语言1, likeNum=1, viewNum=1, commentNum=1, status=1)
key:2  value:BlogStats(id=2, title=java是世界上最好的语言2, likeNum=2, viewNum=2, commentNum=2, status=1)
key:3  value:BlogStats(id=3, title=java是世界上最好的语言3, likeNum=3, viewNum=3, commentNum=3, status=1)
key:4  value:BlogStats(id=4, title=java是世界上最好的语言4, likeNum=4, viewNum=4, commentNum=4, status=1)
key:5  value:BlogStats(id=5, title=java是世界上最好的语言5, likeNum=5, viewNum=5, commentNum=5, status=1)
key:6  value:BlogStats(id=6, title=java是世界上最好的语言6, likeNum=6, viewNum=6, commentNum=6, status=1)
key:7  value:BlogStats(id=7, title=java是世界上最好的语言7, likeNum=7, viewNum=7, commentNum=7, status=1)
key:8  value:BlogStats(id=8, title=java是世界上最好的语言8, likeNum=8, viewNum=8, commentNum=8, status=1)
key:9  value:BlogStats(id=9, title=java是世界上最好的语言9, likeNum=9, viewNum=9, commentNum=9, status=1)
更新过后=====================
key:0  value:BlogStats(id=0, title=java是世界上最好的语言0, likeNum=0, viewNum=0, commentNum=0, status=1)
key:1  value:BlogStats(id=1, title=java是世界上最好的语言1, likeNum=1, viewNum=1, commentNum=1, status=1)
key:2  value:BlogStats(id=2, title=java是世界上最好的语言2, likeNum=2, viewNum=2, commentNum=2, status=1)
key:3  value:BlogStats(id=3, title=java是世界上最好的语言3, likeNum=3, viewNum=3, commentNum=3, status=1)
key:4  value:BlogStats(id=4, title=java是世界上最好的语言4, likeNum=4, viewNum=4, commentNum=4, status=1)
key:5  value:BlogStats(id=5, title=java是世界上最好的语言5, likeNum=5, viewNum=5, commentNum=5, status=1)
key:6  value:BlogStats(id=6, title=java是世界上最好的语言6, likeNum=6, viewNum=6, commentNum=6, status=1)
key:7  value:BlogStats(id=7, title=java是世界上最好的语言7, likeNum=7, viewNum=7, commentNum=7, status=1)
key:8  value:BlogStats(id=8, title=java是世界上最好的语言8, likeNum=10, viewNum=10, commentNum=10, status=1)
key:9  value:BlogStats(id=9, title=java是世界上最好的语言9, likeNum=11, viewNum=11, commentNum=11, status=1)
key:10  value:BlogStats(id=10, title=java是世界上最好的语言10, likeNum=12, viewNum=12, commentNum=12, status=1)

Process finished with exit code 0

从打印的结果来看,id在0-7的没有发生改变,8-9发生了改变,并且新增了一条id为10的新数据,所以这次的改造时成功的,现在已经优化的很好的,但还没有使用到设计模式中的原型模式,那我们还有必要使用吗?

我们之前的操作是直接基于成员变量mapCache,但是我们现在需求稍微做了一下改动,我现在希望,我们需要一次性计算好结果在替换掉成员变量mapCache,而不是一次一次的替换,所以这个时候上面的这种方法就不行了,out了,那我们应该怎么办呢?很多人都想到了

  • 第一步:从数据库中获取到status = 1 的所有数据,也就是已经更新过的数据,这个数据和内存中的一样。
  • 第二步:查询出status=0的所有数据,也就是没有更新过的数据。
  • 第三步:新建一个map用于计算修改后的结果。
  • 第四步:将新建的map替换掉成员变量mapCache。

这样基本上就大功告成了,但是你发现一个问题没有,那就是在第一步的时候会耗费大量的时间,为什么这么说呢?如果数据量过大,HashMap的存储是需要进行hash的计算以及还有可能发生的hash碰撞,所以对象的创建会耗费大量的时间,我们的内存中又存在着一份和我们数据库中差不多的数据,为什么我们不使用它来进行操作呢?这个时候你可能疑惑了,不是说不能对他进行修改吗,要等所有数据修改完成之后一次性替换吗?这个时候原型模式就要上场了。

原型模式两大数据拷贝

不知道大家有没有听过这句话,new出来的在堆里,引用的在栈中,那我们一起来看看Java的两种拷贝模式,浅拷贝和深拷贝。

浅拷贝

浅拷贝就是拷贝指向对象的指针(java中的引用地址),意思就是说:拷贝出来的目标对象的指针和源对象的指针指向的内存空间是同一块空间,浅拷贝只是一种简单的拷贝,让几个对象公用一个内存,然而当内存销毁的时候,指向这个内存空间的所有指针需要重新定义,不然会造成野指针错误。

我们来画个图说明一下,算了,不画了,实在找不到比较好的画图工具,画的太难看了,还不如直接讲,如果有好的画图工具,可以评论告诉我哦,十分感谢。

通俗一点来说,浅拷贝就是拷贝数据的引用地址,并没有拷贝真正的数据,也就是说,拷贝出来的对象和原始对象共享一套数据,也就是说拷贝的数据发生改变,原数据也会发生改变,原数据发生改变,拷贝的数据也会发生改变,因为他们共享一套数据。

下面我们使用浅拷贝来实现一下我们上面的demo,由于Map是没有clone()方法的,所以我需要将之前的Map改成HashMap,请看demo

package com.ymy.test;


import com.ymy.entity.BlogStats;

import java.util.HashMap;
import java.util.Map;

public class PrototypeDesignPattern {



    /**
     * 内存中存放的数据
     * key:博客id
     * value:博客信息
     */
    private static HashMap<Integer, BlogStats> mapCache = new HashMap<Integer, BlogStats>();


    /**
     * 数据库存放的数据
     * key:博客id
     * value:博客信息
     */
    private static HashMap<Integer, BlogStats>  mapMysql = new HashMap<Integer, BlogStats>();

    /**
     * 最近一次的修改时间
     */
    private static Long updateTime = 1585882804208L;


    static {
        BlogStats blogStats = null;
        //初始化内存中java的博客信息
        for(int i = 0; i< 10 ;++i){
           blogStats = BlogStats.builder()
                   .id(i)
                   .title("java是世界上最好的语言"+i)
                   .likeNum(i)
                   .viewNum(i)
                   .commentNum(i)
                   .status(1)
                   .build();
           mapCache.put(i,blogStats);
        }

        //初始化数据库中java的博客信息
        for(int i = 0; i<= 10 ;++i){
            blogStats = BlogStats.builder()
                    .id(i)
                    .title("java是世界上最好的语言"+i)
                    .likeNum(i+2)
                    .viewNum(i+2)
                    .commentNum(i+2)
                    .status(i >= 8 ? 0 : 1)
                    .build();
            mapMysql.put(i,blogStats);
        }
    }

    /**
     * 每天定时更新
     */
    private static void update(Map<Integer, BlogStats> changeData){
        //这是浅拷贝
        HashMap<Integer,BlogStats> oldData = (HashMap<Integer, BlogStats>) mapCache.clone();

        for(Map.Entry<Integer, BlogStats> entry : changeData.entrySet()){
            Integer id = entry.getKey();
            BlogStats blogStats = entry.getValue();
            blogStats.setStatus(1);
            if(oldData.containsKey(id)){
                BlogStats bs = oldData.get(id);
                bs.setLikeNum(blogStats.getLikeNum());
                bs.setViewNum(blogStats.getViewNum());
                bs.setCommentNum(blogStats.getCommentNum());
            }else{
                oldData.put(id,blogStats);
            }
        }
        System.out.println("克隆的HashMap最终结果如下=====================");
        print(oldData);
        System.out.println("克隆对象修改完成之后,原数据如下================");
        print(mapCache);
        mapCache = oldData;
    }


    /**
     * 根据更新的状态获取到最新需要修改的数据
     * 由于这里仅仅只是模拟,所以这里我就随便造几条数据,你们主要看思想即可
     * @return
     */
    public static Map<Integer,BlogStats> getChangeData(){

        //在数据库中查询还没有更新过的数据
        //由于我是模拟,所以我直接伪造了几条数据充当需要修改的数据,还请见谅
        Map<Integer,BlogStats> changeDatas= new HashMap<Integer,BlogStats>();
        for(Map.Entry<Integer, BlogStats> entry : mapMysql.entrySet()){
            Integer id = entry.getKey();
            BlogStats blogStats = entry.getValue();
            if(blogStats.getStatus() == 0){
                changeDatas.put(id,blogStats);
            }
        }
        return changeDatas;
    }


    public static void main(String[] args) {
        System.out.println("内存中最开始存在的数据");
        print(mapCache);
        //获取修改的数据
        Map<Integer, BlogStats> changeData = getChangeData();
        //更新内存数据
        update(changeData);

        //将修改的博客状态改为1并写回数据库,这段省略
        System.out.println("更新过后内存中的数据=====================");
        print(mapCache);
    }


    private static void print( Map<Integer, BlogStats> map ){
        for(Map.Entry<Integer, BlogStats> entry : map.entrySet()){
            System.out.println("key:"+entry.getKey()+"  value:"+entry.getValue());
        }

    }
}

这里面做的修改就是使用了一个局部变量oldData接收mapCache克隆结果,然后对克隆的结果做修改,代码中使用的mapCache.clone()属于浅拷贝,之前说过浅拷贝的数据是共享的,那我们对拷贝过后的数据进行修改,那原数据会不会受到影响呢?我们一起看打印结果

内存中最开始存在的数据
key:0  value:BlogStats(id=0, title=java是世界上最好的语言0, likeNum=0, viewNum=0, commentNum=0, status=1)
key:1  value:BlogStats(id=1, title=java是世界上最好的语言1, likeNum=1, viewNum=1, commentNum=1, status=1)
key:2  value:BlogStats(id=2, title=java是世界上最好的语言2, likeNum=2, viewNum=2, commentNum=2, status=1)
key:3  value:BlogStats(id=3, title=java是世界上最好的语言3, likeNum=3, viewNum=3, commentNum=3, status=1)
key:4  value:BlogStats(id=4, title=java是世界上最好的语言4, likeNum=4, viewNum=4, commentNum=4, status=1)
key:5  value:BlogStats(id=5, title=java是世界上最好的语言5, likeNum=5, viewNum=5, commentNum=5, status=1)
key:6  value:BlogStats(id=6, title=java是世界上最好的语言6, likeNum=6, viewNum=6, commentNum=6, status=1)
key:7  value:BlogStats(id=7, title=java是世界上最好的语言7, likeNum=7, viewNum=7, commentNum=7, status=1)
key:8  value:BlogStats(id=8, title=java是世界上最好的语言8, likeNum=8, viewNum=8, commentNum=8, status=1)
key:9  value:BlogStats(id=9, title=java是世界上最好的语言9, likeNum=9, viewNum=9, commentNum=9, status=1)
克隆的HashMap最终结果如下=====================
key:0  value:BlogStats(id=0, title=java是世界上最好的语言0, likeNum=0, viewNum=0, commentNum=0, status=1)
key:1  value:BlogStats(id=1, title=java是世界上最好的语言1, likeNum=1, viewNum=1, commentNum=1, status=1)
key:2  value:BlogStats(id=2, title=java是世界上最好的语言2, likeNum=2, viewNum=2, commentNum=2, status=1)
key:3  value:BlogStats(id=3, title=java是世界上最好的语言3, likeNum=3, viewNum=3, commentNum=3, status=1)
key:4  value:BlogStats(id=4, title=java是世界上最好的语言4, likeNum=4, viewNum=4, commentNum=4, status=1)
key:5  value:BlogStats(id=5, title=java是世界上最好的语言5, likeNum=5, viewNum=5, commentNum=5, status=1)
key:6  value:BlogStats(id=6, title=java是世界上最好的语言6, likeNum=6, viewNum=6, commentNum=6, status=1)
key:7  value:BlogStats(id=7, title=java是世界上最好的语言7, likeNum=7, viewNum=7, commentNum=7, status=1)
key:8  value:BlogStats(id=8, title=java是世界上最好的语言8, likeNum=10, viewNum=10, commentNum=10, status=1)
key:9  value:BlogStats(id=9, title=java是世界上最好的语言9, likeNum=11, viewNum=11, commentNum=11, status=1)
key:10  value:BlogStats(id=10, title=java是世界上最好的语言10, likeNum=12, viewNum=12, commentNum=12, status=1)
克隆对象修改完成之后,原数据如下================
key:0  value:BlogStats(id=0, title=java是世界上最好的语言0, likeNum=0, viewNum=0, commentNum=0, status=1)
key:1  value:BlogStats(id=1, title=java是世界上最好的语言1, likeNum=1, viewNum=1, commentNum=1, status=1)
key:2  value:BlogStats(id=2, title=java是世界上最好的语言2, likeNum=2, viewNum=2, commentNum=2, status=1)
key:3  value:BlogStats(id=3, title=java是世界上最好的语言3, likeNum=3, viewNum=3, commentNum=3, status=1)
key:4  value:BlogStats(id=4, title=java是世界上最好的语言4, likeNum=4, viewNum=4, commentNum=4, status=1)
key:5  value:BlogStats(id=5, title=java是世界上最好的语言5, likeNum=5, viewNum=5, commentNum=5, status=1)
key:6  value:BlogStats(id=6, title=java是世界上最好的语言6, likeNum=6, viewNum=6, commentNum=6, status=1)
key:7  value:BlogStats(id=7, title=java是世界上最好的语言7, likeNum=7, viewNum=7, commentNum=7, status=1)
key:8  value:BlogStats(id=8, title=java是世界上最好的语言8, likeNum=10, viewNum=10, commentNum=10, status=1)
key:9  value:BlogStats(id=9, title=java是世界上最好的语言9, likeNum=11, viewNum=11, commentNum=11, status=1)
更新过后内存中的数据=====================
key:0  value:BlogStats(id=0, title=java是世界上最好的语言0, likeNum=0, viewNum=0, commentNum=0, status=1)
key:1  value:BlogStats(id=1, title=java是世界上最好的语言1, likeNum=1, viewNum=1, commentNum=1, status=1)
key:2  value:BlogStats(id=2, title=java是世界上最好的语言2, likeNum=2, viewNum=2, commentNum=2, status=1)
key:3  value:BlogStats(id=3, title=java是世界上最好的语言3, likeNum=3, viewNum=3, commentNum=3, status=1)
key:4  value:BlogStats(id=4, title=java是世界上最好的语言4, likeNum=4, viewNum=4, commentNum=4, status=1)
key:5  value:BlogStats(id=5, title=java是世界上最好的语言5, likeNum=5, viewNum=5, commentNum=5, status=1)
key:6  value:BlogStats(id=6, title=java是世界上最好的语言6, likeNum=6, viewNum=6, commentNum=6, status=1)
key:7  value:BlogStats(id=7, title=java是世界上最好的语言7, likeNum=7, viewNum=7, commentNum=7, status=1)
key:8  value:BlogStats(id=8, title=java是世界上最好的语言8, likeNum=10, viewNum=10, commentNum=10, status=1)
key:9  value:BlogStats(id=9, title=java是世界上最好的语言9, likeNum=11, viewNum=11, commentNum=11, status=1)
key:10  value:BlogStats(id=10, title=java是世界上最好的语言10, likeNum=12, viewNum=12, commentNum=12, status=1)

Process finished with exit code 0

我们来看:克隆的HashMap最终结果如下=====================
在这里插入图片描述
修改了两行,新增了一行,从上面的打印结果可以看出原数据也发生了相应的变化
在这里插入图片描述
虽然克隆对象中新增的对象没有在原对象中增加,但是修改的属性在原对象中也发生了改变,使用浅拷贝的同学一定要注意这一点。

所以浅拷贝就不能实现我们的需求了,前面还有一个深拷贝还没有说,那它能不能满足我们的要求 呢?

深拷贝

一个引用对象一般来说由两个部分组成:一个具名的Handle,也就是我们所说的声明(如变量)和一个内部(不具名)的对象,也就是具名Handle的内部对象。它在Manged Heap(托管堆)中分配,一般由新增引用对象的New方法是进行创建。深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。举个例子,一个人名叫张三,后来用他克隆(假设法律允许)了另外一个人,叫李四,不管是张三缺胳膊少腿还是李四缺胳膊少腿都不会影响另外一个人。比较典型的就是Value(值)对象,如预定义类型Int32,Double,以及结构(struct),枚举(Enum)等。

根据介绍来说,深度拷贝是可以满足我们需求的,那我们因该如何实现深拷贝呢?

序列化
序列化很好理解,就是将对象序列化,然后再反序列化,这两步之后就会得到一个全新的对象。

核心代码

/**
     * 深拷贝
     *
     * @param obj
     * @return
     */
    private static Object deepColne(Object obj) {
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(obj);
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            return objectInputStream.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

这个就是通过序列化再反序列化的核心代码,然后再看一下完整demo

package com.ymy.test;


import com.ymy.entity.BlogStats;

import java.io.*;
import java.util.HashMap;
import java.util.Map;

public class PrototypeDesignPattern {


    /**
     * 内存中存放的数据
     * key:博客id
     * value:博客信息
     */
    private static HashMap<Integer, BlogStats> mapCache = new HashMap<Integer, BlogStats>();


    /**
     * 数据库存放的数据
     * key:博客id
     * value:博客信息
     */
    private static HashMap<Integer, BlogStats> mapMysql = new HashMap<Integer, BlogStats>();

    /**
     * 最近一次的修改时间
     */
    private static Long updateTime = 1585882804208L;


    static {
        BlogStats blogStats = null;
        //初始化内存中java的博客信息
        for (int i = 0; i < 10; ++i) {
            blogStats = BlogStats.builder()
                    .id(i)
                    .title("java是世界上最好的语言" + i)
                    .likeNum(i)
                    .viewNum(i)
                    .commentNum(i)
                    .status(1)
                    .build();
            mapCache.put(i, blogStats);
        }

        //初始化数据库中java的博客信息
        for (int i = 0; i <= 10; ++i) {
            blogStats = BlogStats.builder()
                    .id(i)
                    .title("java是世界上最好的语言" + i)
                    .likeNum(i + 2)
                    .viewNum(i + 2)
                    .commentNum(i + 2)
                    .status(i >= 8 ? 0 : 1)
                    .build();
            mapMysql.put(i, blogStats);
        }
    }

    /**
     * 每天定时更新
     */
    private static void update(Map<Integer, BlogStats> changeData) {
        //这是浅拷贝
        HashMap<Integer, BlogStats> oldData = (HashMap<Integer, BlogStats>) deepColne(mapCache);

        for (Map.Entry<Integer, BlogStats> entry : changeData.entrySet()) {
            Integer id = entry.getKey();
            BlogStats blogStats = entry.getValue();
            blogStats.setStatus(1);
            if (oldData.containsKey(id)) {
                BlogStats bs = oldData.get(id);
                bs.setLikeNum(blogStats.getLikeNum());
                bs.setViewNum(blogStats.getViewNum());
                bs.setCommentNum(blogStats.getCommentNum());
            } else {
                oldData.put(id, blogStats);
            }
        }
        System.out.println("克隆的HashMap最终结果如下=====================");
        print(oldData);
        System.out.println("克隆对象修改完成之后,原数据如下================");
        print(mapCache);
        mapCache = oldData;
    }


    /**
     * 根据更新的状态获取到最新需要修改的数据
     * 由于这里仅仅只是模拟,所以这里我就随便造几条数据,你们主要看思想即可
     *
     * @return
     */
    public static Map<Integer, BlogStats> getChangeData() {

        //在数据库中查询还没有更新过的数据
        //由于我是模拟,所以我直接伪造了几条数据充当需要修改的数据,还请见谅
        Map<Integer, BlogStats> changeDatas = new HashMap<Integer, BlogStats>();
        for (Map.Entry<Integer, BlogStats> entry : mapMysql.entrySet()) {
            Integer id = entry.getKey();
            BlogStats blogStats = entry.getValue();
            if (blogStats.getStatus() == 0) {
                changeDatas.put(id, blogStats);
            }
        }
        return changeDatas;
    }


    public static void main(String[] args) {
        System.out.println("内存中最开始存在的数据");
        print(mapCache);
        //获取修改的数据
        Map<Integer, BlogStats> changeData = getChangeData();
        //更新内存数据
        update(changeData);

        //将修改的博客状态改为1并写回数据库,这段省略
        System.out.println("更新过后内存中的数据=====================");
        print(mapCache);
    }


    /**
     * 深拷贝
     *
     * @param obj
     * @return
     */
    private static Object deepColne(Object obj) {
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(obj);
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            return objectInputStream.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }


    private static void print(Map<Integer, BlogStats> map) {
        for (Map.Entry<Integer, BlogStats> entry : map.entrySet()) {
            System.out.println("key:" + entry.getKey() + "  value:" + entry.getValue());
        }

    }
}

与浅拷贝相比,改动的地方并不多,只有克隆的地方发生了修改,其他地方不变

内存中最开始存在的数据
key:0  value:BlogStats(id=0, title=java是世界上最好的语言0, likeNum=0, viewNum=0, commentNum=0, status=1)
key:1  value:BlogStats(id=1, title=java是世界上最好的语言1, likeNum=1, viewNum=1, commentNum=1, status=1)
key:2  value:BlogStats(id=2, title=java是世界上最好的语言2, likeNum=2, viewNum=2, commentNum=2, status=1)
key:3  value:BlogStats(id=3, title=java是世界上最好的语言3, likeNum=3, viewNum=3, commentNum=3, status=1)
key:4  value:BlogStats(id=4, title=java是世界上最好的语言4, likeNum=4, viewNum=4, commentNum=4, status=1)
key:5  value:BlogStats(id=5, title=java是世界上最好的语言5, likeNum=5, viewNum=5, commentNum=5, status=1)
key:6  value:BlogStats(id=6, title=java是世界上最好的语言6, likeNum=6, viewNum=6, commentNum=6, status=1)
key:7  value:BlogStats(id=7, title=java是世界上最好的语言7, likeNum=7, viewNum=7, commentNum=7, status=1)
key:8  value:BlogStats(id=8, title=java是世界上最好的语言8, likeNum=8, viewNum=8, commentNum=8, status=1)
key:9  value:BlogStats(id=9, title=java是世界上最好的语言9, likeNum=9, viewNum=9, commentNum=9, status=1)
克隆的HashMap最终结果如下=====================
key:0  value:BlogStats(id=0, title=java是世界上最好的语言0, likeNum=0, viewNum=0, commentNum=0, status=1)
key:1  value:BlogStats(id=1, title=java是世界上最好的语言1, likeNum=1, viewNum=1, commentNum=1, status=1)
key:2  value:BlogStats(id=2, title=java是世界上最好的语言2, likeNum=2, viewNum=2, commentNum=2, status=1)
key:3  value:BlogStats(id=3, title=java是世界上最好的语言3, likeNum=3, viewNum=3, commentNum=3, status=1)
key:4  value:BlogStats(id=4, title=java是世界上最好的语言4, likeNum=4, viewNum=4, commentNum=4, status=1)
key:5  value:BlogStats(id=5, title=java是世界上最好的语言5, likeNum=5, viewNum=5, commentNum=5, status=1)
key:6  value:BlogStats(id=6, title=java是世界上最好的语言6, likeNum=6, viewNum=6, commentNum=6, status=1)
key:7  value:BlogStats(id=7, title=java是世界上最好的语言7, likeNum=7, viewNum=7, commentNum=7, status=1)
key:8  value:BlogStats(id=8, title=java是世界上最好的语言8, likeNum=10, viewNum=10, commentNum=10, status=1)
key:9  value:BlogStats(id=9, title=java是世界上最好的语言9, likeNum=11, viewNum=11, commentNum=11, status=1)
key:10  value:BlogStats(id=10, title=java是世界上最好的语言10, likeNum=12, viewNum=12, commentNum=12, status=1)
克隆对象修改完成之后,原数据如下================
key:0  value:BlogStats(id=0, title=java是世界上最好的语言0, likeNum=0, viewNum=0, commentNum=0, status=1)
key:1  value:BlogStats(id=1, title=java是世界上最好的语言1, likeNum=1, viewNum=1, commentNum=1, status=1)
key:2  value:BlogStats(id=2, title=java是世界上最好的语言2, likeNum=2, viewNum=2, commentNum=2, status=1)
key:3  value:BlogStats(id=3, title=java是世界上最好的语言3, likeNum=3, viewNum=3, commentNum=3, status=1)
key:4  value:BlogStats(id=4, title=java是世界上最好的语言4, likeNum=4, viewNum=4, commentNum=4, status=1)
key:5  value:BlogStats(id=5, title=java是世界上最好的语言5, likeNum=5, viewNum=5, commentNum=5, status=1)
key:6  value:BlogStats(id=6, title=java是世界上最好的语言6, likeNum=6, viewNum=6, commentNum=6, status=1)
key:7  value:BlogStats(id=7, title=java是世界上最好的语言7, likeNum=7, viewNum=7, commentNum=7, status=1)
key:8  value:BlogStats(id=8, title=java是世界上最好的语言8, likeNum=8, viewNum=8, commentNum=8, status=1)
key:9  value:BlogStats(id=9, title=java是世界上最好的语言9, likeNum=9, viewNum=9, commentNum=9, status=1)
更新过后内存中的数据=====================
key:0  value:BlogStats(id=0, title=java是世界上最好的语言0, likeNum=0, viewNum=0, commentNum=0, status=1)
key:1  value:BlogStats(id=1, title=java是世界上最好的语言1, likeNum=1, viewNum=1, commentNum=1, status=1)
key:2  value:BlogStats(id=2, title=java是世界上最好的语言2, likeNum=2, viewNum=2, commentNum=2, status=1)
key:3  value:BlogStats(id=3, title=java是世界上最好的语言3, likeNum=3, viewNum=3, commentNum=3, status=1)
key:4  value:BlogStats(id=4, title=java是世界上最好的语言4, likeNum=4, viewNum=4, commentNum=4, status=1)
key:5  value:BlogStats(id=5, title=java是世界上最好的语言5, likeNum=5, viewNum=5, commentNum=5, status=1)
key:6  value:BlogStats(id=6, title=java是世界上最好的语言6, likeNum=6, viewNum=6, commentNum=6, status=1)
key:7  value:BlogStats(id=7, title=java是世界上最好的语言7, likeNum=7, viewNum=7, commentNum=7, status=1)
key:8  value:BlogStats(id=8, title=java是世界上最好的语言8, likeNum=10, viewNum=10, commentNum=10, status=1)
key:9  value:BlogStats(id=9, title=java是世界上最好的语言9, likeNum=11, viewNum=11, commentNum=11, status=1)
key:10  value:BlogStats(id=10, title=java是世界上最好的语言10, likeNum=12, viewNum=12, commentNum=12, status=1)

Process finished with exit code 0

这个时候我们发现,即使拷贝的数据发生了变化,原始数据还是原来的数据,这就是深度拷贝,深度拷贝有一个比较致命的缺点,那就是拷贝的时间太长,因为它不仅需要拷贝引用,同时还需要拷贝数据,相对于浅拷贝而言效率太差,当然深拷贝的实现方式不止这一种,还有很多,我在这里就不做过多的展示了。

这里有一点需要特别注意,深度拷贝的对象需要实现序列化,也就是:
在这里插入图片描述
否则会导致程序报错。

既然浅拷贝会导致数据共享,深拷贝印象对象创建的效率,那我们有没有一种这种的方案来解决上面的这个问题呢?可以先想一想,不要着急看下面,因为下面肯定有答案,没有的话我也肯定不会让你们想的。

浅拷贝+深拷贝

对于上面的需求来说,我们需要修改发生改变的博客信息,但是需要修改的数据并不是很多,可能只有几条,也有可能只有几十条,再几十万数据中修改几条使用深拷贝就有点过粪了,所以这里我们采用一种比较折中的方式:浅拷贝+深拷贝

如何实现呢?我们首先将所有的数据心境浅拷贝,然后遇到需要修改的数据再进行深拷贝即可。

改造代码

package com.ymy.test;


import com.ymy.entity.BlogStats;

import java.io.*;
import java.util.HashMap;
import java.util.Map;

public class PrototypeDesignPattern {


    /**
     * 内存中存放的数据
     * key:博客id
     * value:博客信息
     */
    private static HashMap<Integer, BlogStats> mapCache = new HashMap<Integer, BlogStats>();


    /**
     * 数据库存放的数据
     * key:博客id
     * value:博客信息
     */
    private static HashMap<Integer, BlogStats> mapMysql = new HashMap<Integer, BlogStats>();

    /**
     * 最近一次的修改时间
     */
    private static Long updateTime = 1585882804208L;


    static {
        BlogStats blogStats = null;
        //初始化内存中java的博客信息
        for (int i = 0; i < 10; ++i) {
            blogStats = BlogStats.builder()
                    .id(i)
                    .title("java是世界上最好的语言" + i)
                    .likeNum(i)
                    .viewNum(i)
                    .commentNum(i)
                    .status(1)
                    .build();
            mapCache.put(i, blogStats);
        }

        //初始化数据库中java的博客信息
        for (int i = 0; i <= 10; ++i) {
            blogStats = BlogStats.builder()
                    .id(i)
                    .title("java是世界上最好的语言" + i)
                    .likeNum(i + 2)
                    .viewNum(i + 2)
                    .commentNum(i + 2)
                    .status(i >= 8 ? 0 : 1)
                    .build();
            mapMysql.put(i, blogStats);
        }
    }

    /**
     * 每天定时更新
     */
    private static void update(Map<Integer, BlogStats> changeData) {
        //这是浅拷贝
        HashMap<Integer, BlogStats> oldData = (HashMap<Integer, BlogStats>) mapCache.clone();

        for (Map.Entry<Integer, BlogStats> entry : changeData.entrySet()) {
            Integer id = entry.getKey();
            BlogStats blogStats = entry.getValue();
            blogStats.setStatus(1);
            if (oldData.containsKey(id)) {
                //我们先删除数据,因为浅拷贝对删除/新增是不会改变原数据的属性的
               oldData.remove(id);
            }
            //这里再重新赋值
            oldData.put(id,blogStats);
        }
        System.out.println("克隆的HashMap最终结果如下=====================");
        print(oldData);
        System.out.println("克隆对象修改完成之后,原数据如下================");
        print(mapCache);
        mapCache = oldData;
    }


    /**
     * 根据更新的状态获取到最新需要修改的数据
     * 由于这里仅仅只是模拟,所以这里我就随便造几条数据,你们主要看思想即可
     *
     * @return
     */
    public static Map<Integer, BlogStats> getChangeData() {

        //在数据库中查询还没有更新过的数据
        //由于我是模拟,所以我直接伪造了几条数据充当需要修改的数据,还请见谅
        Map<Integer, BlogStats> changeDatas = new HashMap<Integer, BlogStats>();
        for (Map.Entry<Integer, BlogStats> entry : mapMysql.entrySet()) {
            Integer id = entry.getKey();
            BlogStats blogStats = entry.getValue();
            if (blogStats.getStatus() == 0) {
                changeDatas.put(id, blogStats);
            }
        }
        return changeDatas;
    }


    public static void main(String[] args) {
        System.out.println("内存中最开始存在的数据");
        print(mapCache);
        //获取修改的数据
        Map<Integer, BlogStats> changeData = getChangeData();
        //更新内存数据
        update(changeData);

        //将修改的博客状态改为1并写回数据库,这段省略
        System.out.println("更新过后内存中的数据=====================");
        print(mapCache);
    }


    /**
     * 深拷贝
     *
     * @param obj
     * @return
     */
    private static Object deepColne(Object obj) {
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(obj);
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            return objectInputStream.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }


    private static void print(Map<Integer, BlogStats> map) {
        for (Map.Entry<Integer, BlogStats> entry : map.entrySet()) {
            System.out.println("key:" + entry.getKey() + "  value:" + entry.getValue());
        }

    }
}

这样既提高了创建对象的速度,还保证了修改的时候不会对原数据造成影响,是一个非常不错的解决方案。

总结

什么时候使用原型模式?
1.对象创建过于复杂:对象创建的时候需要经过计算、排序等操作。
2.对象时间过长:需要查询数据库或者通过rpc调用。
3.对象过多:比如一个集合中有一百万个对象,我现在需要修改这一百万的对象的数据,但是原数据不能动,所以这个时候使用原型模式比较适合。

java的原型模式我们使用的比较少,那是因为java给我们提供了丰富的克隆方法,其实原型模式的核心就是拷贝,掌握了深拷贝与浅拷贝,你差不多也就掌握了原型模式的精髓了,文章中案例可能举得不是很好,还请大家见谅。

浅拷贝:只拷贝对象的引用,不拷贝对象的数据,拷贝较快。

深拷贝:既拷贝对象的引用也拷贝对象的数据,耗时较久。

这两种拷贝方式可以根据自己的实际情况选择。

发布了44 篇原创文章 · 获赞 165 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_33220089/article/details/105262772