最近在看之前一个自己写的项目代码的时候,发现之前构造的责任链像个楼梯台阶一样的堆在那里,很是影响代码的美观性,并且一条链上的七、八个对象在每次请求时都需要创建一遍,对于一个高并发的项目来说,是一笔不小的开销,于是想对这一块的代码进行优化,而享元模式刚好满足我的需求。
享元模式(Flyweight)是 以共享的方式有效地支持大量的细粒度对象 。能做到共享的关键是区分 内部状态(Internal State) 和 外部状态(External State) 。
内部状态 :存储在享元对象内部,且不会随环境改变而改变的状态。
外部状态 :随环境改变而改变,不可以共享的状态,必须由客户端保存,并在享元对象被创建之后,在需要使用的时候以参数形式传入到享元对象内部。
外部状态不可以影响享元对象的内部状态,即外部状态和内部状态是独立的 。
享元模式就是将内部状态分离出来共享,称之为享元,通过共享享元对象来减少内存的占用,并将外部状态分离出来,放到外部让客户端去维护,并在需要的时候传递给享元对象使用。
享元模式分为 单纯享元模式 和 复合享元模式 。
单纯享元模式
单纯享元模式所定义的角色如下:
此角色是所有具体享元的超类,规定了具体享元需要实现的公共接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public interface AbstractFlyweight { void operate (Object externalState) ; }
实现了抽象享元的具体的类,如果有内部状态的话,必须为内部状态提供存储空间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class ConcreteFlyweight implements AbstractFlyweight { private Object internalState; public ConcreteFlyweight (Object internalState) { this .internalState = internalState; } @Override public void operate (Object externalState) { } }
负责创建和管理享元角色,需要保证享元角色被适当的共享,当客户端需要一个享元对象的时候,工厂需要检查是否有已定义的合适的对象可供使用,如果没有则创建新的享元对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class FlyweightFactory { private Map<String, AbstractFlyweight> map = new ConcurrentHashMap<>(); private static final FlyweightFactory INSTANCE = new FlyweightFactory(); private FlyweightFactory () { } public static FlyweightFactory getInstance () { return INSTANCE; } public AbstractFlyweight getFlyweight (String key) { AbstractFlyweight flyweight = map.get(key); if (null == flyweight) { flyweight = new ConcreteFlyweight("some internal state" ); map.put(key, flyweight); } return flyweight; } }
维护一个对所有享元对象的引用,自行存储所有享元对象的外部状态。客户端不可以直接创建享元对象实例,而必须从工厂中获取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Client { public static void main (String[] args) { FlyweightFactory factory = FlyweightFactory.getInstance(); AbstractFlyweight flyweight = factory.getFlyweight("any key" ); flyweight.operate("any external state" ); } }
介绍完了单纯享元定义的各个角色,我以一个自己的实际项目来举例说明。游客账号是我们常见的一种账号类型,考虑到一般用户名密码的登录方式门槛较高,很多用户不愿意去登录,所以对于一些安全性要求不高的业务来说,我们可以为用户生成游客账号。比如在我的项目里,我选择了一些元素包括设备IMEI信息、安全芯片ID等信息生成游客账号,如果实在没有可以参考的因素,则直接生成随机的游客账号。在代码层面,针对每一种类型的账号,我都为其设计了相应的处理器,并且每个处理器都可以设置降级委托处理器,当当前处理器处理失败时,可以采用降级处理器处理。如果不用享元模式,那么每次来一个请求,我都需要根据具体的类型创建相应的处理器对象,然后处理请求,考虑到游客账号的请求量一般较大,所以有必要尽量避免创建一些不必要的对象。
我们先来分析一下这里面哪些是内部状态,哪些是外部状态,因为享元模式的核心就是要将内部状态和外部状态区分开来,将内部状态封装在享元内部实现共享,而外部状态则有客户端传递进来。很显然,游客账号生成的参考元素是外部状态,因为每一个设备都有不同的设备IMEI信息,有的设备甚至还有安全芯片,这些东西必须由外面传递进来,而一个处理器的降级处理器则是内部状态,因为降级处理器一旦设定,不会动态变化。具体代码的实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public enum VisitorType { DEFAULT(0 ), FID(1 ), DEVICE(2 ); private int id; VisitorType(int id) { this .id = id; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public abstract class AbstractVisitorTypeFlyweight { public abstract void createOrRecoverVisitor (String id) ; protected boolean createOrRecoverVisitorInternal () { return RandomUtils.nextInt(0 , 10 ) > 5 ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 public class DefaultVisitorTypeFlyweight extends AbstractVisitorTypeFlyweight { private AbstractVisitorTypeFlyweight delegateFlyweight; public DefaultVisitorTypeFlyweight () { } public DefaultVisitorTypeFlyweight (AbstractVisitorTypeFlyweight delegateFlyweight) { this .delegateFlyweight = delegateFlyweight; } @Override public void createOrRecoverVisitor (String id) { System.out.println("Create visitor info in default schema!" ); } } public class FidVisitorTypeFlyweight extends AbstractVisitorTypeFlyweight { private AbstractVisitorTypeFlyweight delegateFlyweight; public FidVisitorTypeFlyweight () { } public FidVisitorTypeFlyweight (AbstractVisitorTypeFlyweight delegateFlyweight) { this .delegateFlyweight = delegateFlyweight; } @Override public void createOrRecoverVisitor (String fid) { System.out.println("Create or recover visitor info by fid : " + fid); if (null != delegateFlyweight && !this .createOrRecoverVisitorInternal()) { delegateFlyweight.createOrRecoverVisitor(fid); } } } public class DeviceIdVisitorTypeFlyweight extends AbstractVisitorTypeFlyweight { private AbstractVisitorTypeFlyweight delegateFlyweight; public DeviceIdVisitorTypeFlyweight () { } public DeviceIdVisitorTypeFlyweight (AbstractVisitorTypeFlyweight delegateFlyweight) { this .delegateFlyweight = delegateFlyweight; } @Override public void createOrRecoverVisitor (String deviceId) { System.out.println("Create or recover visitor info by device id : " + deviceId); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 public class VisitorTypeFlyweightFactory { private static final VisitorTypeFlyweightFactory INSTANCE = new VisitorTypeFlyweightFactory(); private Map<VisitorType, AbstractVisitorTypeFlyweight> flyweightMap = new ConcurrentHashMap<>(); private VisitorTypeFlyweightFactory () { } public static VisitorTypeFlyweightFactory getInstance () { return INSTANCE; } public AbstractVisitorTypeFlyweight getFlyweight (VisitorType visitorType) { if (null == visitorType) { return null ; } AbstractVisitorTypeFlyweight flyweight = flyweightMap.get(visitorType); if (null != flyweight) { return flyweight; } switch (visitorType) { case DEFAULT: { flyweight = new DefaultVisitorTypeFlyweight(); flyweightMap.put(VisitorType.DEFAULT, flyweight); break ; } case FID: { flyweight = new FidVisitorTypeFlyweight(new DefaultVisitorTypeFlyweight()); flyweightMap.put(VisitorType.FID, flyweight); break ; } case DEVICE: { flyweight = new DeviceIdVisitorTypeFlyweight(); flyweightMap.put(VisitorType.DEVICE, flyweight); break ; } default : break ; } return flyweight; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class Client { public static void main (String[] args) { VisitorTypeFlyweightFactory factory = VisitorTypeFlyweightFactory.getInstance(); AbstractVisitorTypeFlyweight flyweight = factory.getFlyweight(VisitorType.DEFAULT); flyweight.createOrRecoverVisitor(randomStr()); flyweight = factory.getFlyweight(VisitorType.FID); flyweight.createOrRecoverVisitor(randomStr()); flyweight = factory.getFlyweight(VisitorType.DEVICE); flyweight.createOrRecoverVisitor(randomStr()); } private static String randomStr () { return RandomStringUtils.randomAlphanumeric(32 ); } }
通过上面的代码实现,可以发现享元工厂是整个享元模式实现共享的核心实现所在,通过对享元对象的缓存来保证每一个享元都只有一份,不过在实现之前要区分好内部状态和外部状态,不然会存在线程安全问题。
复合享元模式
复合享元由单纯享元复合而成,一般来说复合享元的组成元素是随外部状态而变化的,所以复合享元通常被描述为不可共享的,这里的不可共享是指这个复合享元不可共享,但是组成复合享元的单纯享元还是共享的,但是我觉得如果外部状态的种类是有限的,我们也可以将复合享元设计成可共享的。
复合享元定义了 5 种角色:抽象享元、具体享元、复合享元、享元工厂,客户端。其中除了复合享元,其余角色定义与单纯享元角色定义相同。 复合享元 所代表的对象是不可以共享的(所以也称作不可共享的享元对象),一个复合享元对象可以分解成多个单纯享元对象,复合享元具有以下两个责任:
由单纯的享元对象复合而成,因此需要提供 add()
这样的聚集管理方法,一个复合对象具有不同的聚集,并且可以在对象创建之后被修改,所以复合对象的状态是可变的,也就不可共享的。
复合享元对象实现了抽象享元接口。
我还是以一个自己在项目中遇到的实际例子来举例说明,话说有一天我在看自己以前写的项目代码的时候,看到了下面这样的一坨代码:
1 2 3 4 5 6 7 AbstractResultHandler resultHandler = new VisitorPassTokenCreateHandler( new SecurityHintUpdateHandler( new AuthorizationModeHandler( new SuccessResultHandler(), visitorInfo.getVisitorId(), requestParams.getSid()), visitorInfo.getVisitorId(), requestParams.getSid()), visitorInfo);
实际上这一坨代码虽然看起来不好看,但是其中的设计还是还是很灵活的,这里面我采用了责任链模式,并允许随机组合各个 handler,但是在构造上采用了静态构造的方式,所以就有了这样一坨构造的过程,也一直想干掉他。仔细分析一下,这坨代码不光不好看,其中每次创都要创建这么多对象也是不必要的,我们来用复合享元进行重构。
首先对这段代码及其背后的实现做一个抽象的描述,实际项目中我们可能需要对方法结果做一些后置的处理,假设我们有1, 2, 3, 4四个后置结果处理器,并且这些处理器具备优先级,同时可以随机进行组合,对于结果处理器而言,没有必要每次请求都现场创建,所以可以用享元模式进行改造,因为结果处理器是可以随意组合的,所以这里可以采用复合享元来根据外部状态构建合适的复合处理器。
前面有介绍说复合享元是由单纯享元复合而成的,所以我们可以先设计好单纯享元,然后由单纯享元复合成复合享元。区分外部状态和内部状态是享元的关键,对于本例子而言,处理器的优先级是内部状态,而待处理的结果则是外部状态,单纯享元的实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public abstract class AbstractResultHandler implements Comparable <AbstractResultHandler > { protected static final int BASE_PRIORITY = 0 ; public abstract void handle (Object obj) ; public abstract int getPriority () ; @Override public int compareTo (AbstractResultHandler handler) { return handler.getPriority() - this .getPriority(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 public class FirstResultHandler extends AbstractResultHandler { @Override public void handle (Object obj) { System.out.println("first handle" ); } @Override public int getPriority () { return BASE_PRIORITY + 40 ; } } public class SecondResultHandler extends AbstractResultHandler { @Override public void handle (Object obj) { System.out.println("second handle" ); } @Override public int getPriority () { return BASE_PRIORITY + 30 ; } } public class ThirdResultHandler extends AbstractResultHandler { @Override public void handle (Object obj) { System.out.println("third handle" ); } @Override public int getPriority () { return BASE_PRIORITY + 20 ; } } public class FourthResultHandler extends AbstractResultHandler { @Override public void handle (Object obj) { System.out.println("fourth handle" ); } @Override public int getPriority () { return BASE_PRIORITY + 10 ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 public class ResultHandlerFactory { private Map<String, AbstractResultHandler> handlerMap = new ConcurrentHashMap<>(4 ); private static volatile ResultHandlerFactory instance; private ResultHandlerFactory () { } public static ResultHandlerFactory getInstance () { if (null == instance) { synchronized (ResultHandlerFactory.class) { if (null == instance) { instance = new ResultHandlerFactory(); } } } return instance; } public AbstractResultHandler getResultHandler (String key) { if (StringUtils.isBlank(key)) { return null ; } AbstractResultHandler handler = handlerMap.get(key); if (null != handler) { return handler; } switch (key) { case "1" : { handler = new FirstResultHandler(); handlerMap.put("1" , handler); break ; } case "2" : { handler = new SecondResultHandler(); handlerMap.put("2" , handler); break ; } case "3" : { handler = new ThirdResultHandler(); handlerMap.put("3" , handler); break ; } case "4" : { handler = new FourthResultHandler(); handlerMap.put("4" , handler); break ; } default : } return handler; } }
构造好了单纯的享元,我们再来复合实现复合享元:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class CompositeResultHandler extends AbstractResultHandler { private List<AbstractResultHandler> handlers = new ArrayList<>(); @Override public void handle (Object obj) { for (final AbstractResultHandler handler : handlers) { handler.handle(obj); } } @Override public int getPriority () { return BASE_PRIORITY; } public void add (AbstractResultHandler handler) { handlers.add(handler); Collections.sort(handlers); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public class CompositeResultHandlerFactory { private ResultHandlerFactory resultHandlerFactory = ResultHandlerFactory.getInstance(); private static class InnerClass { private static final CompositeResultHandlerFactory INSTANCE = new CompositeResultHandlerFactory(); } private CompositeResultHandlerFactory () { } public static CompositeResultHandlerFactory getInstance () { return InnerClass.INSTANCE; } public CompositeResultHandler getCompositeResultHandler (String key) { if (StringUtils.isBlank(key)) { return null ; } CompositeResultHandler compositeHandler = new CompositeResultHandler(); for (int i = 0 ; i < key.length(); i++) { AbstractResultHandler handler = resultHandlerFactory.getResultHandler(String.valueOf(key.charAt(i))); if (null != handler) { compositeHandler.add(handler); } } return compositeHandler; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Client { public static void main (String[] args) { CompositeResultHandlerFactory factory = CompositeResultHandlerFactory.getInstance(); AbstractResultHandler handler = factory.getCompositeResultHandler("124" ); handler.handle(new Object()); } }
享元模式的应用场景
满足下列所有条件时,可以考虑使用享元模式:
一个系统有大量的对象
这些对象消耗着大量的内存
这些对象中的大部分都可以外部化
这些对象可以按照内部状态分成很多的组,当把外部对象从对象中剔除时,每一个组都可以仅用一个对象表示
软件对象不依赖于这些对象的身份,即这些对象是不可分辨的
享元模式的优缺点
大幅度降低了内存中对象的数量
系统设计更加复杂,将享元独享的状态外部化,拉长运行时间