目录
一、享元模式是什么
在面向对象程序设计中,有时要创建大量相同或相似实例对象,会耗费很多的系统资源,非常影响系统性能。而享元模式就是为了解决类似的系统性能的问题。享元模式是为提升系统性能而生的设计模式之一,主要通过复用大对象(重量级对象),以节省内存空间和对象创建时间。
-
【定义】:运用共享技术有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来减少创建对象的数量,以减少内存占用和提高性能。属于结构型模式。
-
【本质】:缓存共享对象,降低内存消耗
享元模式是对象池的一种实现。类似于线程池,避免出现大量重复的创建销毁对象的场景,减少内存的使用。
-
【两个概念】:内部状态、外部状态
-
内部状态:在享元对象内部不随外界环境改变而改变的共享部分。
-
外部状态:随着环境的改变而改变,不能够共享的状态就是外部状态。
由于享元模式区分了内部状态和外部状态,所以可以通过设置不同的外部状态使得相同的对象可以具备一些不同的特性,而内部状态设置为相同部分。内部状态存储于享元对象内部,而外部状态则应该由客户端来考虑。
-
-
享元模式(FlyWeight Pattern)又叫蝇量模式,运用共享技术有效支撑大量细粒度的对象
-
常用于系统底层开发,解决系统性能问题。比如:
数据库连接池
,里面都是创建好的连接对象,在这些对象中有我们需要的则可以直接拿来使用,避免重新创建,如果没有我们需要的,则重新创建一个。 -
享元模式能够解决
重复对象浪费内存的问题
,当系统中有大量相似的对象,需要缓冲池。不需要总是创建对象,可以从缓冲池里面拿。这样可以降低系统内存,提高使用效率。 -
享元模式经典的使用场景就是
池技术
,String常量池、数据库连接池、缓冲池等都是享元模式的应用。享元模式是池技术的重要实现方式。
二、享元模式的适用场景
当系统中多处需要同一组信息时,可以把这些信息封装到一个对象中,并对该对象进行缓存,使得一个对象给多个访问者使用。避免大量同一对象的多次创建,降低大量内存空间的消耗。
-
系统中存在大量的相同或相似对象,造成内存浪。
-
大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
-
需要缓冲池的场景(存在大量的享元实例);
-
ThreadPool 线程池 与数据库连接池 都有使用享元模式
三、享元模式结构
-
抽象享元(Flyweight)角色:享元接口,具体享元类的基类,定义具体享元内在状态公共接口,非享元的外部状态以参数的形式通过方法传入。
-
具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口,指定共享的内部状态。
-
非享元(Unsharable Flyweight)角色:不可共享的外部状态,以参数的形式注入具体享元的相关方法中。
-
享元工厂(Flyweight Factory)角色:负责创建和管理享元角色,并对外提供访问共享享元的接口
当客户对象请求享元对象时,享元工厂会提供一个已经创建的享元对象或者新建一个(如果不存在)
-
客户端(Client)角色:维持一个对享元对象的引用,计算或存储享元的外部状态。
四、享元模式实现方式
-
将需要改写为享元的类成员拆分为内在状态,外在状态两个部分
-
内在状态:包含不变的、 可在许多对象中重复使用的数据的成员变量。
-
外在状态:包含每个对象各自不同的情景数据的成员变量
-
-
保留类中表示内在状态的成员变量, 并将其属性设置为不可修改。这些变量仅可在构造函数中获得初始数值。
-
为方法新增一个外在状态的参数,用于传输外在状态。
-
创建工厂类管理享元缓存池,负责新建并提供享元对象
-
客户端必须存储和计算外在状态 ,调用享元对象的方法。
外在状态和引用享元的成员变量可以移动到单独的类中
五、享元模式的实现
【案例】:抢火车票,车票的信息有:起始站,终点站,价格,座位类别
【案例分析】:在刷票过程中起始站,终点站一般来说是固定的,价格,座位类别可能不一样。
-
抽象享元(Flyweight)角色
/** * 抽象享元(Flyweight)角色 : 火车 */ publicinterface Train { /** * 定义具体享元内在状态公共接口 火车信息 * @param ticketInformation */ void showInfo(TicketInformation ticketInformation); }
-
具体享元(Concrete Flyweight)角色
/** * 具体享元(Concrete Flyweight)角色 */ publicclass 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)角色* */ publicclass 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)角色 */ publicclass TrainFactory { /** * 缓存机制 */ privatestatic 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)); }
-
案例输出结果
六、享元模式的优缺点
-
优点
-
相同或者相似的对象只有一个,降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力
-
享元模式的外部状态相对独立,不影响内部状态,使得享元对象可以在不同的环境中被共享。
-
-
缺点
-
增加程序的复杂性:为了使对象可以共享,需要将一些不能共享的状态外部化。
-
读取享元模式的外部状态会使得运行时间长。
-
七、享元模式和其他模式的区别
-
享元模式是【工厂方法模式】的一个改进,享元模式同样要求创建一个或一组对象,并且就是通过工厂方法模式生成对象的,只不过享元模式为工厂方法模式增加了缓存。
-
可以使用【享元模式】实现【组合模式】的共享树叶节点以节省内存。
-
【享元模式】是生成大量的小型对象,【外观模式】是用一个对象来代表整个子系统。
-
当只有一个享元对象时,【享元模式】和【单例模式】类似。都是一个对象背复用,不同点在于:
-
享元模式可以有多个实体, 其内在状态也可以不同,单例模式则是严格控制单个进程中只有一个实例对象
-
享元模式可以实现对外部的单例,也可以在需要的使用创建更多的对象。
-
八、注意事项和细节
-
在享元模式这样理解,“享”就表示共享,“元”表示对象
-
系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式
-
用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable存储
-
享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
-
享元模式
提高了系统的复杂度
。需要分离出内部状态
和外部状态
,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方. -
使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
-
享元模式经典的应用场景是需要缓冲池的场景,比如 String常量池、数据库连接池
九、总结
-
享元模式虽然减少系统中对象的数量。但也导致引起系统的逻辑更加复杂。
-
主要通过核心的享元工厂类来共享享元对象。
-
不变的内部状态存储于享元对象内部,可变的外部状态由客户端负责。