设计模式~享元模式

155 阅读3分钟

1. 介绍

  • 享元模式是对象池的一种实现,用来尽可能地减少内存使用量,适用于可能存在大量重复对象的场景,用来缓存可共享的对象,避免创建过多对象,从而提升性能、避免内存溢出等。
  • 享元对象中的部分状态是可共享的,称为内部状态,它不会受到环境变化的影响;部分状态是不可共享的,称为外部状态,会受到环境变化的影响。
  • 在享元模式中会建立一个Map对象容器,键为可共享的内部状态,值为对象本身,用容器缓存对象达到重复利用相似对象的目的

2. 定义

  • 享元模式是池技术的重要实现方式,它可有效地支持大量的细粒度对象的重复利用,从而减少应用程序对象的创建,降低程序内存的占用,提高程序的性能。

3. UML类图

image.png

  • Flyweight:享元对象抽象基类或接口
  • ConcreteFlyweight:具体享元对象
  • FlyweightFactory:享元工厂,负责管理享元对象池和创建享元对象

4. 使用场景

  • 系统中存在大量的相似对象。
  • 需要缓冲池的场景。
  • 细粒度的对象都具有较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份。

5. 简单实现

  • 需求:每年的春运,高铁站卖高铁票,服务器不可能每卖出一张票就创建一个高铁票对象,而是使用享元模式将高铁票对象进行处理,这里只是简单模拟,将高铁票车次当作不可共享的外部状态,高铁票价格以及座位当作可共享的内部状态。
  • 定义享元对象接口
/**
 * 定义享元高铁票对象接口
 *
 * @author BTPJ  2022/8/17
 */
public interface ITicket {

    /**
     * 展示车票信息
     *
     * @param seat 座位类型(包括一等座、二等座、无座)
     */
    void showTicketInfo(String seat);
}
  • 高铁票实际对象
/**
 * 具体高铁票对象
 *
 * @author BTPJ  2022/8/17
 */
public class Ticket implements ITicket {
    private final String number; // 车次
    private int price; // 车票价格
    private String seat; // 座位类型

    public Ticket(String number) {
        this.number = number;
    }

    @Override
    public void showTicketInfo(String seat) {
        if ("一等座".equals(seat)) {
            price = 400 + (int) (Math.random() * 400);  // 模拟价格
        } else {
            price = new Random().nextInt(400);  // 模拟价格
        }
        System.out.println(number + "车次-" + seat + "-价格:" + price);
    }
}
  • 高铁票享元工厂类
/**
 * 车票享元工厂
 *
 * @author BTPJ  2022/8/17
 */
public class TicketFactory {

    private static HashMap<String, ITicket> map = new HashMap<>();

    public static ITicket getTicket(String number) {
        if (map.containsKey(number)) {
            System.out.println("使用享元模式缓存的对象" + number);
            return map.get(number);
        } else {
            System.out.println("创建对象并缓存" + number);
            ITicket ticket = new Ticket(number);
            map.put(number, ticket);
            return ticket;
        }
    }
}
  • main方法调用类
/**
 * 调用类
 *
 * @author BTPJ  2022/8/17
 */
public class Client {

    public static void main(String[] args) {
        ITicket ticket1 = TicketFactory.getTicket("G111");
        ticket1.showTicketInfo("一等座");
        ITicket ticket2 = TicketFactory.getTicket("G111");
        ticket2.showTicketInfo("二等座");

        ITicket ticket3 = TicketFactory.getTicket("G222");
        ticket3.showTicketInfo("无座");
    }
}

运行结果:
创建对象并缓存G111
G111车次-一等座-价格:509
使用享元模式缓存的对象G111
G111车次-二等座-价格:382
创建对象并缓存G222
G222车次-无座-价格:66

6. 源码中的使用场景

  • Android的Handler机制中的Message对象(享元模式的思想,使用了链表结构存储对象,而不是传统的map存储)

7. 优缺点

  • 优点:
    • 大幅度减少内存中对象的数量,对象重复利用,避免频繁GC,提高程序性能
  • 缺点:
    • 享元模式使得系统更加复杂。为了对象可共享,需将一些状态外部化,并得对享元对象进行管理,使逻辑变复杂
    • 将享元状态外部化,而读取外部状态会导致运行时间稍稍变长