设计模式(13):享元模式

尺有所短,寸有所长;不忘初心,方得始终。

一、享元模式是什么

在面向对象程序设计中,有时要创建大量相同或相似实例对象,会耗费很多的系统资源,非常影响系统性能。而享元模式就是为了解决类似的系统性能的问题。享元模式是为提升系统性能而生的设计模式之一,主要通过复用大对象(重量级对象),以节省内存空间和对象创建时间。

  • 【定义】:运用共享技术有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来减少创建对象的数量,以减少内存占用和提高性能。属于结构型模式。

  • 【本质】缓存共享对象,降低内存消耗

    享元模式是对象池的一种实现。类似于线程池,避免出现大量重复的创建销毁对象的场景,减少内存的使用。

  • 【两个概念】:内部状态、外部状态

    • 内部状态:在享元对象内部不随外界环境改变而改变的共享部分。

    • 外部状态:随着环境的改变而改变,不能够共享的状态就是外部状态。

      由于享元模式区分了内部状态和外部状态,所以可以通过设置不同的外部状态使得相同的对象可以具备一些不同的特性,而内部状态设置为相同部分。内部状态存储于享元对象内部,而外部状态则应该由客户端来考虑。

二、享元模式的适用场景

当系统中多处需要同一组信息时,可以把这些信息封装到一个对象中,并对该对象进行缓存,使得一个对象给多个访问者使用。避免大量同一对象的多次创建,降低大量内存空间的消耗。

  • 系统中存在大量的相同或相似对象,造成内存浪。
  • 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
  • 需要缓冲池的场景(存在大量的享元实例);
  • ThreadPool 线程池 与数据库连接池 都有使用享元模式

三、享元模式结构

  • 抽象享元(Flyweight)角色:享元接口,具体享元类的基类,定义具体享元内在状态公共接口,非享元的外部状态以参数的形式通过方法传入。

  • 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口,指定共享的内部状态。

  • 非享元(Unsharable Flyweight)角色:不可共享的外部状态,以参数的形式注入具体享元的相关方法中。

  • 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色,并对外提供访问共享享元的接口

    当客户对象请求享元对象时,享元工厂会提供一个已经创建的享元对象或者新建一个(如果不存在)

  • 客户端(Client)角色:维持一个对享元对象的引用,计算或存储享元的外部状态。

设计模式(13):享元模式

四、享元模式实现方式

  • 将需要改写为享元的类成员拆分为内在状态,外在状态两个部分

    • 内在状态:包含不变的、 可在许多对象中重复使用的数据的成员变量。
    • 外在状态:包含每个对象各自不同的情景数据的成员变量
  • 保留类中表示内在状态的成员变量, 并将其属性设置为不可修改。这些变量仅可在构造函数中获得初始数值。

  • 为方法新增一个外在状态的参数,用于传输外在状态。

  • 创建工厂类管理享元缓存池,负责新建并提供享元对象

  • 客户端必须存储和计算外在状态 ,调用享元对象的方法。

    外在状态和引用享元的成员变量可以移动到单独的类中

五、享元模式的实现

【案例】:抢火车票,车票的信息有:起始站,终点站,价格,座位类别

【案例分析】:在刷票过程中起始站,终点站一般来说是固定的,价格,座位类别可能不一样。

  • 抽象享元(Flyweight)角色

    /**
    * 抽象享元(Flyweight)角色 : 火车
    */

    public interface Train {
    /**
    * 定义具体享元内在状态公共接口 火车信息
    * @param ticketInformation
    */

    void showInfo(TicketInformation ticketInformation);
    }

  • 具体享元(Concrete Flyweight)角色

    /**
    * 具体享元(Concrete Flyweight)角色
    */

    public class TrainTicket implements Train{
    /**
    * 共享的内部状态 起始站,终点站
    */

    private String startingStation;
    private String terminal;


    public TrainTicket(String startingStation, String terminal) {
    this.startingStation = startingStation;
    this.terminal = terminal;
    }

    /**
    * 实现抽象享元角色中所规定的接口
    * @param t
    */

    @Override
    public void showInfo(TicketInformation t) {
    System.out.println(String.format("这张车票是由%s开往%s:座位是%s,价格:%s 元",
    startingStation, terminal, t.getBunk(), t.getPrice()));
    }
    }

  • 非享元(Unsharable Flyweight)角色

    /**
    * 非享元(Unsharable Flyweight)角色*
    */

    public class TicketInformation {

    /**
    * 不可共享的外部状态
    */

    private String bunk;
    private Integer price;

    public TicketInformation(String bunk, Integer price) {
    this.bunk = bunk;
    this.price = price;
    }

    public String getBunk() {
    return bunk;
    }

    public void setBunk(String bunk) {
    this.bunk = bunk;
    }

    public Integer getPrice() {
    return price;
    }

    public void setPrice(Integer price) {
    this.price = price;
    }
    }

  • 享元工厂(Flyweight Factory)角色

    /**
    * 享元工厂(Flyweight Factory)角色
    */

    public class TrainFactory {
    /**
    * 缓存机制
    */

    private static Map<String, Train> trainPool = new ConcurrentHashMap<>();

    public static Train queryTicket(String startingStation, String terminal) {
    String key = startingStation + "--->>>>" + terminal;
    if (TrainFactory.trainPool.containsKey(key)) {
    return TrainFactory.trainPool.get(key);
    }
    /**
    * 创建对象 new TrainTicket
    * 当有多个具体的享元对象时,这里可以吧类名当做key ,通过反射或者注入的方式生成相应的享元对象
    */

    Train ticket = new TrainTicket(startingStation, terminal);
    TrainFactory.trainPool.put(key, ticket);
    return ticket;
    }

    }

  • 客户端代码实现

        public static void main(String[] args) throws Exception {
    Train train = TrainFactory.queryTicket("深圳北", "武汉");
    train.showInfo(new TicketInformation("商务座",1500));
    train = TrainFactory.queryTicket("深圳北", "武汉");
    train.showInfo(new TicketInformation("一等座",800));
    train = TrainFactory.queryTicket("深圳北", "武汉");
    train.showInfo(new TicketInformation("二等座",500));
    }
  • 案例输出结果

设计模式(13):享元模式

六、享元模式的优缺点

  • 优点
    • 相同或者相似的对象只有一个,降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力
    • 享元模式的外部状态相对独立,不影响内部状态,使得享元对象可以在不同的环境中被共享。
  • 缺点
    • 增加程序的复杂性:为了使对象可以共享,需要将一些不能共享的状态外部化。
    • 读取享元模式的外部状态会使得运行时间长。

七、享元模式和其他模式的区别

  • 享元模式是【工厂方法模式】的一个改进,享元模式同样要求创建一个或一组对象,并且就是通过工厂方法模式生成对象的,只不过享元模式为工厂方法模式增加了缓存

  • 可以使用【享元模式】实现【组合模式】的共享树叶节点以节省内存。

  • 享元模式】是生成大量的小型对象,【外观模式】是用一个对象来代表整个子系统。

  • 当只有一个享元对象时,【享元模式】和【单例模式】类似。都是一个对象背复用,不同点在于:

    • 享元模式可以有多个实体, 其内在状态也可以不同,单例模式则是严格控制单个进程中只有一个实例对象
    • 享元模式可以实现对外部的单例,也可以在需要的使用创建更多的对象。

八、总结

  • 享元模式虽然减少系统中对象的数量。但也导致引起系统的逻辑更加复杂。
  • 主要通过核心的享元工厂类来共享享元对象。
  • 不变的内部状态存储于享元对象内部,可变的外部状态由客户端负责。


原文始发于微信公众号(星河之码):设计模式(13):享元模式

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/27141.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!