Java设计模式(14)-享元模式
享元模式,"享"即共享,"元"即元素,软件中则为对象,享元就是共享对象之意。这种设计模式用来共享对象而不是大量创建对象,以节约系统资源。现实中,很多东西都可以使用享元模式来解决,比如围棋、五子棋,棋子的颜色就黑白两种,只是他们在棋盘的位置不同;又如,展示类网站,多个用户公用一套系统,只是内容和展示形式存在差异;再如,教室的课桌和凳子…上述示例都有一个共同点:相关的东西存在很大的相似,但是也不完全相同,此时如果要开发软件,那么都可以用享元模式来设计。 1. 从围棋游戏开始 假设要你开发一款围棋游戏,我们知道,围棋棋子有黑色和白色两种,棋子在棋盘上的位置随着下棋的进行而不同,那么你怎么设计类关系? 常规的思路是使用工厂模式,棋手每落下一枚棋子,那么就创建一个棋子实例,并设置它放置的位置: Figure 1. 使用工厂模式 但是,围棋有 19 × 19 = 361 个交叉点,假设每个点上全部都有棋子,那么足足需要创建361个对象!这无疑是对系统资源的浪费。 那么,有好的解决办法吗?答案是使用享元模式来设计。 2. 什么是享元模式 享元模式(flyweight),也叫蝇量模式,它强调对象的细粒度控制和共享,主张运用共享技术有效地支持对象的复用。"享"即共享,"元"即元素,软件中则为对象,说白了就是最大程度的让对象可以共享并复用。享元模式的类结构如图所示: Figure 2. 享元模式类结构 如图所示,可以看出,享元模式有如下几种角色: 抽象享元角色(Flyweight):具体享元对象的超类或接口,可以接受作用于外部状态(见后文); 具体享元角色(ConcreteFlyweight):继承或实现抽象享元角色,为内部状态增加存储空间; 非共享享元角色(UnsharedConcreteFlyweight):同样继承或实现抽象享元角色,但是这些类并不需要共享出来; 享元工厂角色:管理Flyweight对象,确保合理的共享他们。它往往提供获取Flyweight对象的方法,当对象存在时直接返回,否则创建一个。 提示 有的书上也将UnsharedConcreteFlyweight对象单独提出来,将其看做外部状态的抽象,它并不实现Flyweight,而是作为Flyweight接口方法的参数进行传递,以说明它不共享。 享元模式的结构也好理解:抽象一个享元接口,提供通用的方法,然具体享元对象实现该接口,但是并不是所有逇具体享元对象都需要共享,因此按需共享并拆分,最后由享元工厂统一管理他们。上边还提到两个概念:外部状态和内部状态,它们是什么? 2.1. 外部状态和内部状态 在享元模式中,对象状态按是否共享分为两种:共享和不共享,随着环境变化而改变的、不可以共享的状态称为外部状态;相反,不会随着环境变化而改变的状态称为内部状态。 使用享元模式,重点是要分析出哪些对象是外部状态,哪些是内部状态,并将外部状态单独抽象出来,作为一个变化的部分。 在前边围棋的例子中,棋子的颜色只有黑、白两种,它们不会随着下棋的进行而增加或者减少,因此棋子的颜色是内部状态。而棋子在棋盘上放置的位置,随着下棋的进行,棋子的位置都会不同,因此,棋子的位置是外部状态。 又比如,多个用户共用一个网站,网站可以作为Flyweight对象而共享,而网站的代码、模板、数据库都是不会变化(增加或减少)的,它们可作为内部状态来共享。但是,不同的用户账号是不同的,每个用户都有自己额账号,因此,账号可以作为外部状态。此时就可以将账号单独提取出来作为一个变化的实体: Figure 3. 共享网站的设计类图 3. 围棋游戏改进 我们再来看看如何使用享元模式来解决围棋中重复创建对象、浪费资源的问题。 首先,前边已经分析了,围棋棋子的颜色是内部状态,而其在棋盘的位置是外部状态。那么,我们可以设计如下的类图: Figure 4. 围棋使用享元模式设计的类图 内部对象定义为Color类,外部对象抽象为Position类,Piece为抽象的Flyweight对象,WeiqiPiece作为具体的享元对象实现了Piece接口。最后,这些对象通过WeiqiPieceFactory统一管理。示例代码如下: 1、定义外部状态类: // 外部状态: 棋子位置 class Position { private final int x; private final int y; public Position(int x, int y) { this.x = x; this.y = y; } public String position() { (1) return "(" + x + "," + y + ")"; } } ...