享元模式以共享的方式高效地支持大量的细粒度对象。能做到共享的关键是内蕴状态和外蕴状态。
内蕴状态是存储在享元对象内部的,并且是不会随环境变化而有所不同。因此,一个享元可以具有内蕴状态并且可以共享。
外蕴状态是随环境改变而改变的、不可以共享的状态。享元对象的外蕴状态必须由客户端保存,并在享元对象创建之后,在需要使用的时候再传到享元对象内部。
外蕴状态不可以影响享元对象的内蕴状态。换句话说,它们是独立的。
1. 分类
根据所涉及的享元对象的内部表象,享元模式可以分成单纯享元模式和复合享元模式。
单纯享元模式结构示意图如下:
复合享元模式的结构示意图如下:
在复合享元模式中,享元对象构成合成模式。因此,复合享元模式实际上是单纯享元模式与合成模式的组合。
2. 单纯享元模式结构
在单纯享元模式中,所有的享元对象都是可以共享的。类图如下:
角色如下:
抽象享元角色:所有具体享元类的超类,为这些类规定出需要实现的公共接口。外蕴状态(External State)可以通过参数形式传入。
具体享元角色:实现抽象享元角色规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的环境无关,从而使得享元对象可以在系统内共享。(此角色不能手动创建,必须通过下面的享元工厂创建)
享元工厂角色:本角色负责创建和管理享元对象以保证享元对象可以共享,通常是单例模式。
代码如下:
package cn.qlq.flyweight; public abstract class Flyweight { // 参数state是外蕴状态 abstract public void operation(String state); }
package cn.qlq.flyweight; public class ConcreteFlyweight extends Flyweight { private Character instrinsicState = null; public ConcreteFlyweight(Character instrinsicState) { this.instrinsicState = instrinsicState; } @Override public void operation(String state) { System.out.println("instrinsicState -> " + instrinsicState + ";Extrinsic state -> " + state); } }
package cn.qlq.flyweight; import java.util.HashMap; import java.util.Map; public class FlyweightFactory { private Map<Character, Flyweight> files = new HashMap<>(); private static FlyweightFactory factory = new FlyweightFactory(); public static FlyweightFactory getInstace() { return factory; } private FlyweightFactory() { } public Flyweight build(Character state) { if (files.containsKey(state)) { return files.get(state); } Flyweight flyweight = new ConcreteFlyweight(state); files.put(state, flyweight); return flyweight; } }
客户端代码:
package cn.qlq.flyweight; public class Client { public static void main(String[] args) { FlyweightFactory instace = FlyweightFactory.getInstace(); // 向享元工厂对象请求一个内蕴状态为'a'的享元对象 Flyweight flyweight = instace.build('a'); // 以参量的方式传入一个外蕴状态 flyweight.operation("FirstCall"); // 向享元工厂对象请求一个内蕴状态为'a'的享元对象 Flyweight flyweight2 = instace.build('a'); // 以参量的方式传入一个外蕴状态 flyweight2.operation("SecondCall"); } }
虽然两次调用,但是由于内蕴状态相同,所以只创建同一个享元对象。
3. 复合享元模式的结构
在上面的单纯享元模式中,所有的享元对象都是单纯享元对象,也就是说都是可以直接共享的。
考虑一种复杂情况,即将一些单纯享元模式使用合成模式加以复合,形成复合享元对象。这样的复合享元对象本身不能共存,但是可以分解成单纯享元对象以共享。
类图如下:
角色如下:
抽象享元角色:所有具体享元类的超类,为这些类规定出需要实现的公共接口。外蕴状态(External State)可以通过参数形式传入。
具体享元角色:实现抽象享元角色规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的环境无关,从而使得享元对象可以在系统内共享。(此角色不能手动创建,必须通过下面的享元工厂创建)
复合享元角色:复合享元角色代表的对象是不可共享的,但是一个复合享元对象可以分解成多个对象本身是单纯享元对象的组合。复合享元角色又称做不可共享的享元对象。(此角色不能手动创建,必须通过下面的享元工厂创建)
享元工厂角色:本角色负责创建和管理享元对象以保证享元对象可以共享,通常是单例模式。与上面单纯模式不同的是该工程也可以创建复合享元角色对象。
代码:
package cn.qlq.flyweight; public abstract class Flyweight { // 参数state是外蕴状态 abstract public void operation(String state); }
package cn.qlq.flyweight; public class ConcreteFlyweight extends Flyweight { private Character instrinsicState = null; public ConcreteFlyweight(Character instrinsicState) { this.instrinsicState = instrinsicState; } @Override public void operation(String state) { System.out.println("instrinsicState -> " + instrinsicState + ";Extrinsic state -> " + state); } }
复合享元对象:
由单纯享元对象复合而成,提供add这样的聚集管理方法。
package cn.qlq.flyweight; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; public class ConcreteCompositeFlyweight extends Flyweight { private Map<Character, Flyweight> files = new HashMap<>(); public void add(Character character, Flyweight flyweight) { files.put(character, flyweight); } @Override public void operation(String state) { Flyweight flyweight = null; for (Iterator it = files.entrySet().iterator(); it.hasNext();) { Map.Entry<Character, Flyweight> entry = (Entry<Character, Flyweight>) it.next(); flyweight = entry.getValue(); flyweight.operation(state); } } }
享元工厂:在上面的基础上增加创建复合享元对象的方法。
package cn.qlq.flyweight; import java.util.HashMap; import java.util.Map; public class FlyweightFactory { private Map<Character, Flyweight> files = new HashMap<>(); private static FlyweightFactory factory = new FlyweightFactory(); public static FlyweightFactory getInstace() { return factory; } private FlyweightFactory() { } /** * 单纯享元工厂,所需状态以参量传入 * * @param state * @return */ public Flyweight build(Character state) { if (files.containsKey(state)) { return files.get(state); } Flyweight flyweight = new ConcreteFlyweight(state); files.put(state, flyweight); return flyweight; } /** * 复合享元工厂,所需状态以参量形式传入,这个参量类型可以是内蕴状态的集合或数据组(此处正好是Character,所以可以用String) * * @param compositeState * @return */ public Flyweight build(String compositeState) { ConcreteCompositeFlyweight compositeFlyweight = new ConcreteCompositeFlyweight(); int length = compositeState.length(); Character state = null; for (int i = 0; i < length; i++) { state = new Character(compositeState.charAt(i)); System.out.println("state -> " + state); compositeFlyweight.add(state, this.build(state)); } return compositeFlyweight; } }
客户端代码:
package cn.qlq.flyweight; public class Client { public static void main(String[] args) { FlyweightFactory instace = FlyweightFactory.getInstace(); // 向享元工厂对象请求一个内蕴状态为'a'的享元对象 Flyweight flyweight = instace.build('a'); // 以参量的方式传入一个外蕴状态 flyweight.operation("FirstCall"); // 向享元工厂对象请求一个内蕴状态为'a'的享元对象 Flyweight flyweight2 = instace.build('a'); // 以参量的方式传入一个外蕴状态 flyweight2.operation("SecondCall"); System.out.println("================"); // 复合享元对象 Flyweight flyweight3 = instace.build("bcd"); flyweight3.operation("CompositeCall"); } }
结果:
instrinsicState -> a;Extrinsic state -> FirstCall
instrinsicState -> a;Extrinsic state -> SecondCall
================
state -> b
state -> c
state -> d
instrinsicState -> b;Extrinsic state -> CompositeCall
instrinsicState -> c;Extrinsic state -> CompositeCall
instrinsicState -> d;Extrinsic state -> CompositeCall
4. 享元模式适用场景与优缺点
4.1 使用场景
遇到下列情况可以使用:
(1)一个系统有大量的对象。
(2)这些对象耗费大量的内存
(3)这些对象的状态中的大部分都可以外部化。
(4)这些对可以按照内蕴状态分成很多的组,当把外蕴状态从对象中剔除时,每一个组都可以仅用一个对象代替。
(5)系统不依赖这些对象的身份。换言之这些对象是不可分辨的。
使用享元模式需要维护一个记录系统中已有的所有享元的表,而这些需要耗费资源。因此,应在有足够多的享元实例时才值得使用享元模式。
4.2 优缺点
优点在于大幅度地降低内存中对象的数量。
缺点是:享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序逻辑变复杂;状态外部化,而读取外部状态使得运行时间稍微边长。
享元模式的应用参考序列建生成器:https://www.cnblogs.com/qlqwjy/p/11011679.html