特点与定义
- 定义:
运用共享技术来有効地支持大量细粒度对象的复用,是一种对象结构型模式。
享元模式要求能够共享的对象必须是细粒度对象。它通过共享已经存在的又橡来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
- 问题由来
在面向对象程序设计过程中,有时会面临要创建大量相同或相似对象实例的问题。例如,围棋和五子棋中的黑白棋子,图像中的坐标点或颜色,这些对象有很多相似的地方,如果能把它们相同的部分提取出来共享,则能节省大量的系统资源,这就是享元模式的产生背景。
享元模式状态
在了解享元模式之前我们先要了解两个概念:内部状态、外部状态。
- 内部状态:在享元对象内部不随外界环境改变而改变的共享部分。
- 外部状态:随着环境的改变而改变,不能够共享的状态就是外部状态。
享元模式的实现关键就是区分应用中的这两种状态,并将外部状态外部化
参与角色
Flyweight
(抽象享元角色):定义了具体享元类的公共行为,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态),非享元的外部状态以参数的形式通过方法传入。Concrete Flyweight
(具体享元)角色:实现抽象享元角色中所规定的接口,为享元对象提供了内存空间来保存享元对象的内部数据,通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。Unsharable Flyweight
(非共享享元)角色:并不是所有的享元类都需要被共享的,有的享元类就不要被共享,可以通过享元类来实例一个非共享享元对象。是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。Flyweight Factory
(享元工厂)角色:负责创建和管理享元对象。它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),返回新创建的实例并将其存储在享元池中。
- 类结构图
结构代码示例
在享元模式中引入了享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,当用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。典型的享元工厂类的代码如下:
定义享元抽象(Flyweight)
//抽象享元角色 abstract class Flyweight { //内部状态 public String intrinsic; //外部状态 protected UnsharableFlyweight extrinsic; //要求享元角色必须接受外部状态 public void setExtrinsic(UnsharableFlyweight extrinsic) { this.extrinsic = extrinsic; } //定义一些公共行为,这些行为可以来设置或提供(内部、外部状态) public abstract void operation(); }
定义具体享元(ConcreteFlyweight)
class ConcreteFlyweight extends Flyweight{ public void operation() { System.out.println("具体享元对象"); } }
定义非共享享元
class UnsharableFlyweight { int x; int y; public UnsharableFlyweight(int x, int y) { this.x = x; this.y = y; } }
定义享元工厂
class FlyweightFactory { //定义一个池容器 private static Map<String, Flyweight> pool = new HashMap<>(); //享元工厂 public static Flyweight getFlyweight(String key) { Flyweight flyweight = null; if(pool.containsKey(key)) { //池中有该对象 flyweight = pool.get(key); System.out.print("已有 " + key + " 直接从池中取---->"); } else { //根据外部状态创建享元对象 flyweight = new ConcreteFlyweight(); //放入池中 pool.put(key, flyweight); System.out.print("创建 " + key + " 并从池中取出---->"); } return flyweight; } }
客户端调用代码
public class FlyweightTest { public static void main(String[] args) { String key = "first"; //第一次获取 Flyweight flyweight1 = FlyweightFactory.getFlyweight(key); flyweight1.setExtrinsic(new UnsharableFlyweight(10,20)); System.out.println(flyweight1); //第二次获取 Flyweight flyweight2 = FlyweightFactory.getFlyweight(key); flyweight2.setExtrinsic(new UnsharableFlyweight(30,20)); System.out.println(flyweight2); } }
模式案例分析
现在我们使用享元模式实现如下功能:每瓶冰红茶饮料盖上都会有一个二维码,扫描二维码就知道是否中奖,我们简化二维码为中奖文字,都会在瓶盖上,瓶盖的形状和颜色是可以提取为相似的部分的,然后中奖信息每个瓶盖都不一样,所以为有差异的非共享成员。
定义抽象享元接口
abstract class RedTea { //内部状态,容量 public String capacity; //外部状态,二维码内容 protected QRCodeContent extrinsic; //要求享元角色必须接受外部状态 abstract String viewQRCodeContent(QRCodeContent extrinsic); }
定义非共享对象接口
//非共享角色,二维码内容,每瓶都不一样 class QRCodeContent { String qrCode; public QRCodeContent() { qrCode = UUID.randomUUID().toString(); } public String wrapperContent() { qrCode = "二维码唯一ID:" + qrCode + "\n" + "根据ID得到中奖信息:" + (new Random().nextInt(6) + 1) + "等奖"; return qrCode; } }
定义具体享元接口
class ConcreteRedTea extends RedTea{ public ConcreteRedTea(String capacity) { this.capacity = capacity; } @Override String viewQRCodeContent(QRCodeContent extrinsic) { return "茶壶容量:" + this.capacity + "\n" + "二维码内容:" + extrinsic.wrapperContent(); } }
定义享元工厂
class FlyweightFactory { //定义一个池容器 private static Map<String, RedTea> pool = new HashMap<>(); //享元工厂 public static RedTea getFlyweight(String capacity) { if(pool.containsKey(capacity)) { //池中有该对象 return pool.get(capacity); } else { //放入池中 pool.put(capacity, new ConcreteRedTea(capacity)); return pool.get(capacity); } } }
客户端调用
public class Client { public static void main(String[] args) { String capacity = "1.5L"; //第一次获取 RedTea flyweight1 = FlyweightFactory.getFlyweight(capacity); String viewQRCode1 = flyweight1.viewQRCodeContent(new QRCodeContent()); System.out.println(viewQRCode1); //第二次获取 RedTea flyweight2 = FlyweightFactory.getFlyweight(capacity); String viewQRCode2 = flyweight2.viewQRCodeContent(new QRCodeContent()); System.out.println(viewQRCode2); } }
所谓的享元模式就是利用共享池技术,将重复对象存储起来(内部状态),不同的因素由外部传入(外部状态),充分提高对象的重复利用率,提高性能
总结
优点
- 享元模式的优点在于它能够极大的减少系统中对象的个数。
- 享元模式由于使用了外部状态,外部状态相对独立,不会影响到内部状态,所以享元模式使得享元对象能够在不同的环境被共享。
缺点
- 由于享元模式需要区分外部状态和内部状态,使得应用程序在某种程度上来说更加复杂化了。
- 为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。
适用场景
- 如果一个系统中存在大量的相同或者相似的对象,由于这类对象的大量使用,会造成系统内存的耗费,可以使用享元模式来减少系统中对象的数量。
- 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
补充
- 享元模式可以极大地减少系统中对象的数量。但是它可能会引起系统的逻辑更加复杂化。
- 享元模式的核心在于享元工厂,它主要用来确保合理地共享享元对象。
- 内部状态为不变共享部分,存储于享元享元对象内部,而外部状态是可变部分,它应当由客户端来负责。