享元(Flyweight)模式,享元模式以共享的方式高效的支持大量的细粒度对象。享元模式能做到共享的关键是区分内蕴状态和外蕴状态。内蕴状态存储在享元内部,不会随环境的改变而有所不同。外蕴状态是随环境的改变而改变的。
单纯享元模式所涉及到的角色如下:
享元模式是一个比较不好理解的设计模式,很多文章上都以 String 为例,来解释享元模式,String 虽然是符合享元模式设计的,但是很多人还是理解不了,因为享元模式有个内蕴状态和外蕴状态的概率,至于什么是内蕴状态,什么是外蕴状态,很多人都没有说清楚。
咱们先从享元模式适用的场景说起。享元模式适用于需要创建大量对象的时候。比如在 Java 里,认为是需要有大量的 String 对象的,Java 不希望为每个 String 对象都开辟一个空间,而是希望如果两个 String 对象的内容一样的话,它们共享一个空间。至于怎么理解 String 的内蕴状态和外蕴状态,我觉得 String 可以理解只有内蕴状态,没有外蕴状态。
咱们再说说线程池。咱们暂且假设对线程池线程的数量没有要求,也就是当需要执行一个操作,就从线程池里查找是否有存在已有相同的任务,如果有,拿来直接使用,如果没有,就创建一个线程。咱们再以 HTTP 请求为例,内蕴状态就是创建 HTTP 连接,发送请求,接受响应数据。而外蕴状态咱们假设就是 URL,如果有相同的 URL 在线程池里,然后就获取这个对象,再 run 一次。如果没有,就创建一个线程,然后 run 一下。
这样的设计时符合享元模式的,但是又不符合咱们平时的使用。咱们一般会规定线程池的大小,假设咱们规定线程池只能有5个,但是请求明显比线程池里线程的个数多,所以咱们一般会有个请求队列,先把请求添加到一个队列里,当线程池里有线程空闲了,就从队列里拿一个请求来执行。当然,这个不是线程自己去做的,是线程池的管理者来做的,也就是享元模式里所说的工厂,工厂维护着一个队列,维护着一个线程集合,当有个线程结束了它的任务后,把自己当前的状态告诉给工厂,工厂会给它分配新的工作,或者让它闲着。如果给它分配新的工作,也就是把新的 URL 告诉它,这个时候,这个 URL 也就是外蕴状态。工厂里从线程池里获取一个对象(拿现成的也好,新创建的也好),然后修改它的外蕴状态。
抽象享元和具体享元,其实可以理解为模版方法模式。想想咱们在设计一个 HTTP 请求工具类的时候,咱们会有个类似于 BaseRequest 的抽象类,这个类里怎么创建连接,发送数据,获取相应,并且有很多错误处理的代码,但是一般会有个一个或者若干个抽象的方法,这些抽象方法是为每个具体的请求设计的,比如每个请求具体的URL和参数,比如请求获得到相应后,怎么解析数据,或者怎么更新 UI,这些需要具体的请求实现类是实现。
咱们再回来看享元模式,享元模式的核心是享元对象可以被适当的共享,享元工厂会负责从已有的享元对象里查找是否有符合要求的享元对象。这里有好几个感性的词,所有不好理解。比如符合要求的享元对象,对于 String 来说,符合要求的是 String 的内容要一模一样的,比如 String a = "abc" ; String b = "abc"; a 和 b 共享一个享元对象,因为 a 和 b 完全一样。但是在线程池里,请求 A 的URL可能是 http://www.baidu.com,请求 B 的 URL 可能是 http://www.google.com ,但是请求 B 可能就复用了请求 A 的对象。因为对于它来说,内蕴状态是一样的。
一个内蕴状态是存储在享元对象内部的,并且是不会随环境的改变而有所不同。因此,一个享元可以具有内蕴状态并可以共享。
一个外蕴状态是随环境的改变而改变的、不可以共享的。享元对象的外蕴状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。外蕴状态不可以影响享元对象的内蕴状态,它们是相互独立的。
所以判断一个享元对象是否符合要求,是根据内蕴状态。而且根据定义,内蕴对象是不随环境变化而变化的,而且是跟着享元对象一起被共享的。
解释得比较乱,还请海涵,确实理解不是很深入。
- EOF -
本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处,尊重他人劳动。
转载请注明:文章转载自 Binkery 技术博客 [https://binkery.com]
本文标题: 设计模式之享元模式
本文地址: https://binkery.com/archives/504.html