跳转至

享元模式

分类:结构型模式。

类结构

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() 的局部变量,没有画成字段;具体树类也没有增加源码不存在的坐标或图形缓存字段。

协作过程

  1. Client 准备 deciduousLocations 与 coniferLocations,各保存三个位置,再创建 TreeFactory。
  2. 工厂构造器立即创建两种树。客户端分别用 getTree("deciduous") 和 getTree("conifer") 取得引用;同一工厂对同一类型的后续请求仍返回同一个对象。
  3. 客户端遍历落叶树坐标,用同一个 DeciduousTree 逐次执行 display(x, y)。该方法还读取 LocalDate.now(),当月份不在三月至十月时输出无叶提示。
  4. 客户端遍历针叶树坐标,用同一个 ConiferTree 显示其余位置。六个位置只使用两个树对象,各位置不进入共享对象字段。

固定版本源码:

评论