原型模式(Prototype Pattern)

概念

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能,这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时(存在一个对象创建过程很复杂的情况,这样就需要选择去复用对象了),则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建(一般都是初始化的数据,另外一种方式则是放到redis这一类缓存数据库中)。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

简介

作用

    - 减少内存资源消耗

    - 提升性能

    - 对象复用,减少冗余代码;

    - 逃避构造函数的约束;这里是可以通过各种手段去创建一个复杂对象;

不足之处

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

    - 必须实现 Cloneable 接口。

    

场景

    - 需要资源优化的

    - 因为创建对象过大,需要提升性能的

    

代码

案例描述

假设我们有一个需求,需要去统计订单数据,以日期为key,返回给前端;但是有三个页面都需要用到这一批数据,首页需要三天的做折线图,报表页只需要最近一天的,其他页面需要最近两天的;那此时我们没必要去数据中去查询三次,将三天的都查询出来,然后放到Map中缓存即可,需要哪天的数据返回给前端自己通过日期key去取出来即可;

工程目录

image.png

项目类图

原型模式.jpg

具体实现

订单统计实体(OrderStatisticBo )

/**
 * 功能描述: 前一日的订单统计
 *
 * @author: WuChengXing
 * @create: 2021-07-13 22:03
 **/
public class OrderStatisticBo {
    /**
     * 昨日成功订单数
     */
    private Double successNum;

    /**
     * 昨日失败订单数
     */
    private Double fairNum;

    /**
     * 昨日交易总金额
     */
    private BigDecimal totalAmount;

    public OrderStatisticBo(Double successNum, Double fairNum, BigDecimal totalAmount) {
        this.successNum = successNum;
        this.fairNum = fairNum;
        this.totalAmount = totalAmount;
    }

    public Double getSuccessNum() {
        return successNum;
    }

    public void setSuccessNum(Double successNum) {
        this.successNum = successNum;
    }

    public Double getFairNum() {
        return fairNum;
    }

    public void setFairNum(Double fairNum) {
        this.fairNum = fairNum;
    }

    public BigDecimal getTotalAmount() {
        return totalAmount;
    }

    public void setTotalAmount(BigDecimal totalAmount) {
        this.totalAmount = totalAmount;
    }
}

统计接口层(Statistic)

/**
 * 功能描述: 统计
 *
 * @author: WuChengXing
 * @create: 2021-07-13 22:01
 **/
public interface Statistic {
    /**
     * 订单相关统计
     * @param
     */
    Map<String, OrderStatisticBo> totalOrder();
}

实现层(StatisticImpl)

/**
 * 功能描述: 统计实现类
 *
 * @author: WuChengXing
 * @create: 2021-07-13 22:08
 **/
public class StatisticImpl implements Statistic {

    static Map<String, OrderStatisticBo> orderStatisticBoMap = new HashMap<>(8);

    @Override
    public Map<String, OrderStatisticBo> totalOrder() {
        if (CollectionUtils.isEmpty(orderStatisticBoMap)) {
            System.out.println("==== 我走的是查询数据库,我需要构建复杂对象,进行初始化 =======");
            orderStatisticBoMap = getOrderStatisticBoForThreeDay();
            return orderStatisticBoMap;
        }
        System.out.println("==== 我走的是缓存,我用了享元模式思想,我不用查数据库去构建复杂对象 =========");
        return orderStatisticBoMap;
    }

    /**
     * 模拟从数据库里取出最近三天的订单统计
     *
     * @return
     */
    private Map<String, OrderStatisticBo> getOrderStatisticBoForThreeDay() {
        Map<String, OrderStatisticBo> map = new HashMap<>(4);
        map.put("2021/07/10", new OrderStatisticBo(10D, 2D, new BigDecimal(5220)));
        map.put("2021/07/11", new OrderStatisticBo(11D, 3D, new BigDecimal(6220)));
        map.put("2021/07/12", new OrderStatisticBo(12D, 4D, new BigDecimal(7220)));
        return map;
    }
}

这里的getOrderStatisticBoForThreeDay() 方法是模拟从数据库中取出数据,假设他是一个非常复杂且性能较差的接口;所以我们应该让其实现复用;

测试

/**
 * 功能描述: 原型模式
 *
 * @author: WuChengXing
 * @create: 2021-07-13 21:47
 **/
public class PrototypeTest {
    static Statistic statistic = new StatisticImpl();

    public static void main(String[] args) {
        totalToIndex();
        totalToReport();
        System.out.println("---- 其他查询 ----");
        statistic.totalOrder();
    }

    public static void totalToIndex() {
        System.out.println("---- 首页的查询 ----");
        statistic.totalOrder();
    }

    public static void totalToReport() {
        System.out.println("---- 报表页的查询 ----");
        statistic.totalOrder();
    }
}

结果:

---- 首页的查询 ----
==== 我走的是查询数据库,我需要构建复杂对象,进行初始化 =======
---- 报表页的查询 ----
==== 我走的是缓存,我用了享元模式思想,我不用查数据库去构建复杂对象 =========
---- 其他查询 ----
==== 我走的是缓存,我用了享元模式思想,我不用查数据库去构建复杂对象 =========