享元模式¶
分类:结构型模式。
类结构¶
classDiagram
direction TB
class Client
class Flyweight {
<<interface>>
+operation(extrinsicState)
}
class ConcreteFlyweight {
-intrinsicState
+operation(extrinsicState)
}
class FlyweightFactory {
-cache
+getFlyweight(key) Flyweight
}
Flyweight <|.. ConcreteFlyweight
FlyweightFactory --> "0..*" Flyweight : 共享缓存
FlyweightFactory ..> ConcreteFlyweight : 创建
Client ..> FlyweightFactory : 获取享元
Client ..> Flyweight : 传入外在状态
角色定义¶
| 角色 | 定义 |
|---|---|
| Client(客户端) | 向工厂请求享元,管理随使用场景变化的外在状态(extrinsicState),并在调用时传入。 |
| Flyweight(享元接口) | 定义接收外在状态的操作,使同一享元能够服务于不同使用场景。 |
| ConcreteFlyweight(具体享元) | 实现享元操作,保存可共享、与场景无关的内在状态(intrinsicState,通常不可变);结合调用时传入的外在状态完成操作,不将该外在状态留作共享字段。 |
| FlyweightFactory(享元工厂) | 按描述内在状态的键维护共享缓存(cache);命中时返回已有享元,未命中时创建、缓存并返回具体享元。 |
Head First 示例:在多个位置复用树木对象¶
享元是《Head First 设计模式》附录介绍的模式。这里采用作者 2020 年第二版配套代码 flyweight:客户端要显示多个位置的落叶树和针叶树,同类树复用一个对象,各棵树的位置则由客户端保存,并在调用 display(int x, int y) 时传入。配套实现用控制台输出演示显示动作,没有实际绘制图像。
该实现比上方通用模型更小:TreeFactory 在构造时就创建一个 DeciduousTree 和一个 ConiferTree,分别存入 d、c,随后按类型返回已有对象,没有延迟创建或映射表缓存。共享范围是同一个工厂实例;源码只用注释提示树干、枝叶等图形数据,没有真实的图形数据字段,不能把这些概念性内在状态画成已经实现的字段。
| 具体类名 | 对应角色 | 职责 |
|---|---|---|
Tree |
享元接口 | 声明接收坐标的 display(),并提供默认方法 isWithinRange() 判断月份范围。 |
DeciduousTree |
具体享元 | 显示落叶树位置,依据当前日期判断是否提示树上无叶,不保存调用传入的坐标。 |
ConiferTree |
具体享元 | 显示针叶树位置,不保存调用传入的坐标。 |
TreeFactory |
享元工厂 | 提前创建两种树,通过 getTree(String type) 返回对应的共享引用。 |
Client |
客户端与外在状态管理者 | 在两个坐标数组中保存位置,取得共享树对象后循环调用 display()。 |
示例 UML¶
classDiagram
direction TB
class Tree {
<<interface>>
+display(int x, int y)
+isWithinRange(LocalDate aDate) boolean
}
class DeciduousTree {
+display(int x, int y)
}
class ConiferTree {
+display(int x, int y)
}
class TreeFactory {
Tree d
Tree c
+TreeFactory()
+getTree(String type) Tree
}
class Client {
+main(String[] args)$
}
Tree <|.. DeciduousTree
Tree <|.. ConiferTree
TreeFactory --> "2" Tree : d 与 c 复用
TreeFactory ..> DeciduousTree : 构造时创建
TreeFactory ..> ConiferTree : 构造时创建
Client ..> TreeFactory : 获取共享对象
Client ..> Tree : display 传入坐标
图中省略日期库类型与非法类型的异常路径;isWithinRange() 是 Tree 已提供方法体的默认方法,两个具体树类只实现 display()。客户端坐标数组属于 main() 的局部变量,没有画成字段;具体树类也没有增加源码不存在的坐标或图形缓存字段。
协作过程¶
Client准备deciduousLocations与coniferLocations,各保存三个位置,再创建TreeFactory。- 工厂构造器立即创建两种树。客户端分别用
getTree("deciduous")和getTree("conifer")取得引用;同一工厂对同一类型的后续请求仍返回同一个对象。 - 客户端遍历落叶树坐标,用同一个
DeciduousTree逐次执行display(x, y)。该方法还读取LocalDate.now(),当月份不在三月至十月时输出无叶提示。 - 客户端遍历针叶树坐标,用同一个
ConiferTree显示其余位置。六个位置只使用两个树对象,各位置不进入共享对象字段。
固定版本源码: