赫连小伍

V1

2022/04/02阅读:30主题:默认主题

设计模式(十一):享元模式

大家好,欢迎来到编程队伍,我是作者王小伍,你可以叫我伍先生

这篇文章是设计模式系列文章的第十一篇:享元模式

设计模式系列前几篇没看的可以点击对应的文章快速查看

设计模式(一):单例模式

设计模式(二):工厂模式

设计模式(三):生成器模式

设计模式(四):原型模式

设计模式(五):适配器模式

设计模式(六):装饰器模式

设计模式(七):桥接模式

设计模式(八):代理模式

设计模式(九):组合模式

设计模式(十):外观模式


正文

我们还是老规矩,用一个具体案例开始我们的设计模式之旅

假如我们要做一个功能,可以生产各种各样的汽车

首先我们得有一个汽车类,类里面有几个成员变量,分别是:汽车型号、轮子、窗户、发动机

public class Car {

    private String model; // 型号
    private String wheel; // 轮子
    private String windows; // 窗户
    private String engine; // 发动机

    public Car(String model, String wheel, String windows, String engine) {
        this.model = model;
        this.wheel = wheel;
        this.windows = windows;
        this.engine = engine;
    }
}

接下来就可以生产汽车了

我们先生产一辆五菱宏光

Car car = new Car("五菱宏光""轮胎""窗户""发动机");

当然,只有这一辆肯定是不够的。我们还要生产更多汽车,可能会有上千万辆

也就是说我们的汽车类Car 要被创建上千万次

那么问题来了,系统中有上千万个 Car 的实例,服务器的内存能抗住吗?

我们的例子中,为了方便大家理解,汽车类Car 成员变量只有4个,实际情况中可能会有很多个

成员变量越多,每个Car的实例占用的内存就越多。上千万个Car实例,肯定会消耗服务器很多内存。如果服务器内存不够就会导致内存溢出

我们需要减少内存的消耗

这时候我们就应该想到要用享元模式了,因为享元模式的目的就是将内存消耗最小化

假设同一型号的汽车,它们的轮胎、窗户、发动机都是一样的

那么,我们就可以让相同型号的汽车,去共享轮胎、窗户、发动机这些成员变量

在享元模式中,轮胎、窗户、发动机这些可以被共享的成员变量称为内在状态;型号这个不能被共享的成员变量称为外在状态

我们根据享元模式的设计思想,把汽车类Car进行拆分

型号这个外在状态的成员变量保持不变;轮胎、窗户和发动机这3个内在状态拆分出去,作为BaseCar方便共享

拆分完成以后,我们就要想办法把BaseCar进行共享,也就是让它只有一个实例

说到这里你可能会想到单例模式,但它并不是单例的

我们之前说了,同一型号才能共享BaseCar

对于不同型号的汽车来说,BaseCar是不同的实例,所以它不是单例的

下面我们用代码演示一下该怎么共享BaseCar

public class BaseCarFactory {

    // map用来存储BaseCar,key是型号
    private static Map<String, BaseCar> map = new HashMap<>();

    // 获取BaseCar对象
    public static BaseCar getBarseCar(String model, String wheel, String windows, String engine) {
        // 如果同一型号的BaseCar在map中存在,则直接使用
        if (map.containsKey(model)) {
            System.out.println(model + "使用了缓存");
            return map.get(model);
        }
        // 如果同一型号的BaseCar在map中不存在,则新生成一个并保存到map中
        System.out.println(model + "没有使用缓存");
        BaseCar baseCar = new BaseCar(wheel, windows, engine);
        map.put(model, baseCar);
        return baseCar;
    }
}

接下来我们生产10辆五菱宏光试一下

for (int i = 0; i < 10; i++) {
    String model = "五菱宏光";
    BaseCar baseCar = BaseCarFactory.getBarseCar(model, "轮胎""窗户""发动机");
    Car car = new Car(model, baseCar);
}

最后输出的结果如下

从运行结果可以看到,只有在第一次时没有使用缓存,后面都使用了缓存

对于五菱宏光这个汽车对象来说,不管要生产多少辆,它的BaseCar的实例只有一个,这就在一定程度上减少了内存的消耗

这样我们就用一个简单的享元模式,完成了减少内存消耗的功能

基本介绍

享元模式是最常用的设计模式之一,在创建型模式、结构型模式和行为型模式分类中,享元模式归属于结构型模式

享元模式中,就是共享,表示一个细粒度的对象,享元模式就是共享细粒度的对象

细粒度对象一定是轻量级的,因此享元模式也被称为轻量级模式

享元模式只有一个目的: 将内存消耗最小化

享元模式中,分为内在状态和外在状态。

  • 可以被共享的,不会发生变化的成员变量作为内在状态
  • 每个对象独有的不能被共享的成员变量作为外在状态

享元模式的实现步骤总共分为两步:

  1. 对一个对象进行分析,抽象出对象的内在状态和外在状态
  2. 对内在状态进行缓存管理,如果内在状态已经被缓存则直接使用;如果内在状态不存在,则新创建一个并缓存

优点

可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而可以降低系统的内存消耗

缺点

1、享元模式虽然节省了内存消耗,但是它会使系统的执行速度变慢

因为内在状态全局只有一份,所有外在状态要共享它。共享就势必会争抢,为了避免争抢就势必会加锁,加锁就会降低执行速度

2、代码结构会变得复杂,不容易理解

本来是一个对象,为什么要拆成内在状态和外在状态两个对象?对于不了解详情的研发人员肯定会有这样的疑问

适用场景

在程序中需要创建大量的相似对象,而又没有足够的内存容量时可以使用享元模式

与其他模式关系

  • 与单例模式的关系

单例模式是全局只有一个实例;享元模式是特定情况下有一个实例

比如,上文的例子中。如果我们的系统只生产五菱宏光这一种车型,那么BaseCar就可以是单例的

单例模式回顾:设计模式(一):单例模式

  • 与工厂模式的关系

我们通常会使用工厂模式+享元模式来管理内在状态的创建

工厂模式回顾:设计模式(二):工厂模式

  • 与外观模式的关系

享元模式是解决了系统中需要生成大量相似对象的问题

外观模式是解决一个对象代表多个子系统的问题

外观模式回顾:设计模式(十):外观模式

最后还是那句话,设计模式不是万能的,只有合理利用设计模式才能写出合理的代码

-- 以上内容来自公众号 编程队伍,转载请注明出处

分类:

后端

标签:

后端

作者介绍

赫连小伍
V1