享元模式

享元模式（英语：Flyweight Pattern）是一种软体设计模式。它使用物件用来尽可能减少记忆体使用量；于相似物件中分享尽可能多的资讯。当大量物件近乎重复方式存在，因而使用大量记忆体时，此法适用。通常物件中的部分状态(state)能够共享。常见做法是把它们放在资料结构外部，当需要使用时再将它们传递给享元。
概述.
典型的享元模式的例子，是文书处理器中用来表示字符（character，亦译为「字元」）的数据结构。在朴素的观念上，文档中的每个字符都有一个字形（glyph）物件，它含有字体（typeface或font family）、字型（font或font face）的重量与大小（比如点数）和其它格式资讯，但这会使得为每个字符都耗用成百上千个位元组。取而代之的是，每个字符参照到一个共享字形物件，此物件会被其它有共同特质的字符所分享；只有每个字符（文件中或页面中）的位置才需要另外储存。
享元对象可以做到：
客户可以重复使用codice_3对象并在需要时传入外在状态，减小免在物理上创建的对象的数目。
结构.
上面左侧的UML类图展示了：
上面右侧的序列图展示了运行时交互：
示例.
C++.
C++标准模板库提供了允许从键映射到唯一性对象的多种容器。使用了这种容器，通过移除创建临时对象的需要，能有助于减少内存使用。
import std;
template &lt;typename K, typename V&gt;
using TreeMap = std::map&lt;K, V&gt;;
template &lt;typename K, typename V&gt;
using HashMap = std::unordered_map&lt;K, V&gt;;
// 租户类Tenant的实例是享元
class Tenant {
private:
 const std::string name;
public:
 Tenant(std::string_view name): 
 nodiscard
 std::string getName() const noexcept {
 return name;
};
// 注册类Registry充当Tenant享元对象的工厂
// 并在租户容器中将租户名字映射到唯一性的租户对象
class Registry {
private:
 HashMap&lt;std::string, Tenant&gt; tenants;
public:
 Registry() = default;
 nodiscard
 Tenant&amp; findByName(std::string_view name) {
 if (!tenants.contains(name)) {
 tenants[name] = Tenant{name};
 return tenants[name];
};
// 公寓类Apartment在房客容器中将房间编号映射到唯一性的租户对象
class Apartment {
private:
 TreeMap&lt;int, Tenant*&gt; occupants;
 Registry registry;
public:
 Apartment() = default;
 void addOccupant(std::string_view name, int room) {
 occupants[room] = &amp;registry.findByName(name);
 void printTenants() {
 for (const std::pair&lt;int, Tenant&gt;&amp; i : occupants) {
 const int&amp; room = i.first;
 const Tenant&amp; tenant = i.second;
 std::println("{} occupies room {}", tenant.name(), room);
int main(int argc, char* argv[]) {
 Apartment apartment;
 apartment.addOccupant("David", 1);
 apartment.addOccupant("Sarah", 3);
 apartment.addOccupant("George", 2);
 apartment.addOccupant("Sarah", 12);
 apartment.addOccupant("Michael", 10);
 apartment.printTenants();
 return 0;
Java.
以下Java程式用来解释上述的文字。这个例子用来解释享元模式利用只加载执行任务时所必需的最少资料，因而减少记忆体使用量。
public enum FontEffect {
 BOLD, ITALIC, SUPERSCRIPT, SUBSCRIPT, STRIKETHROUGH
public final class FontData {
 * FLY_WEIGHT_DATA是WeakHashMap，它是作为享元的FontData对象的缓存。
 * 键值对的键是FontData，如果这个键对象不再被强引用，则丢弃此项的键值对；
 * 键值对的值是用WeakReference包装的FontData，它不会增加键对象的强引用数目。
 private static final WeakHashMap&lt;FontData, WeakReference&lt;FontData» FLY_WEIGHT_DATA =
 new WeakHashMap&lt;FontData, WeakReference&lt;FontData»();
 private final int pointSize;
 private final String fontFace;
 private final Color color;
 private final Set&lt;FontEffect&gt; effects;
 private FontData(int pointSize, String fontFace, Color color, EnumSet&lt;FontEffect&gt; effects) {
 this.pointSize = pointSize;
 this.fontFace = fontFace;
 this.color = color;
 this.effects = Collections.unmodifiableSet(effects);
 
 public static FontData create(int pointSize, String fontFace, Color color,
 FontEffect... effects) {
 EnumSet&lt;FontEffect&gt; effectsSet = EnumSet.noneOf(FontEffect.class);
 for (FontEffect fontEffect : effects) {
 effectsSet.add(fontEffect);
 // 创建FontData对象，如果它与以前创建的对象有关相同的参数值，
 // 那么这个新对象会被舍弃，这种浪费的创建是减少总体内存消耗的代价。
 FontData data = new FontData(pointSize, fontFace, color, effectsSet);
 // 在缓存中检索同新建FontData实例有相同参数值的以前创建的FontData实例，
 // 如果它（仍然）存在，则对这项键值对的值进行解引用从而得到这个以前创建的FontData实例。
 WeakReference&lt;FontData&gt; ref = FLY_WEIGHT_DATA.get(data);
 FontData result = (ref != null) ? ref.get() : null;
 // 如果检索结果没有匹配的实例存在，在缓存中存储新的FontData实例。
 if (result == null) {
 FLY_WEIGHT_DATA.put(data, new WeakReference&lt;FontData&gt; (data));
 result = data;
 // 返回具有给定参数值的单一的不可变的FontData实例。
 // FontData对象是不可变的，所以只有getter方法，没有setter方法。
 return result;
 // 覆写进行键值对的键对象检索用到的对象相等比较运算方法，
 // 将其实现为结果真值是两个对象所包含的参数值的逐项比较的合取。
 @Override
 public boolean equals(Object obj) {
 if (obj instanceof FontData) {
 if (obj == this) {
 return true;
 FontData other = (FontData) obj;
 return other.pointSize == pointSize &amp;&amp; other.fontFace.equals(fontFace)
 &amp;&amp; other.color.equals(color) &amp;&amp; other.effects.equals(effects);
 return false;
 @Override
 public int hashCode() {
 return (pointSize * 37 + effects.hashCode() * 13) * fontFace.hashCode();
Python.
下面是Python的例子:
from abc import ABC, abstractmethod
from weakref import WeakValueDictionary
class Glyph(ABC):
 @abstractmethod 
 def draw(self, text_color='Automatic'): pass
all_font_style = {'Regular', 'Bold', 'Italic',
class GlyphFactory():
 sharable_dict = WeakValueDictionary()
 def __new__(cls, char, font_family,
 font_size, font_style={'Regular'}):
 assert len(char) == 1
 assert font_style &lt;= all_font_style
 key = (char, font_family,
 font_size, frozenset(font_style))
 if key in cls.sharable_dict:
 obj = cls.sharable_dict[key]
 else:
 obj = super().__new__(cls)
 cls.sharable_dict[key] = obj 
 obj.__initialize(char, font_family, 
 font_size, font_style)
 return obj
 def __initialize(self, char, font_family, 
 font_size, font_style={'Regular'}):
 self.char = char
 self.font_family = font_family
 self.font_size = font_size
 self.font_style = font_style
 def info(self):
 return (f"Character: {self.char}, "
 + f"FontFamily: {self.font_family}, "
 + f"FontSize: {self.font_size}, "
 + f"FontStyle: {self.font_style}")
 @classmethod
 def create_para(cls, string, *args, **kwargs): pass
class Character(Glyph, GlyphFactory):
 def draw(self, text_color='Automatic'): pass
class Paragraph(Glyph):
 def __init__(self, *args, **kwargs):
 self.state = GlyphFactory.create_para(*args, **kwargs) 
 def draw(self, text_color='Automatic'): pass
它与惰性初始化中用Python写的类似例子的区别在于，它的目标是共享而非推延，下面是其执行：
»&gt; char1 = Character('A', 'Noto Serif CJK', 24)
»&gt; char1.info()
"Character: A, FontFamily: Noto Serif CJK, FontSize: 24, FontStyle: {'Regular'}"
»&gt; char2 = Character('A', 'Noto Serif CJK', 14, {'Bold'})
»&gt; char2.info()
"Character: A, FontFamily: Noto Serif CJK, FontSize: 14, FontStyle: {'Bold'}"
»&gt; char3 = Character('A', 'Noto Serif CJK', 24)
»&gt; assert char3 is char1
PHP.
下面是PHP的例子：
&lt;?php
class CoffeeFlavour {
 private static array $CACHE = [];
 public static function intern(string $name): self {
 self::$CACHE[$name] ??= new self($name);
 return self::$CACHE[$name];
 public static function flavoursInCache(): int {
 return count(self::$CACHE);
 public function __toString(): string {
 return $this-&gt;name;
class Order {
 private function __construct(
 private CoffeeFlavour $flavour,
 private int $tableNumber
 public static function create(string $flavourName, int $tableNumber): self {
 $flavour = CoffeeFlavour::intern($flavourName);
 return new self($flavour, $tableNumber);
 public function __toString(): string {
 return "Serving {$this-&gt;flavour} to table {$this-&gt;tableNumber}";
class CoffeeShop {
 private array $orders = [];
 public function takeOrder(string $flavour, int $tableNumber) {
 $this-&gt;orders[] = Order::create($flavour, $tableNumber);
 public function service() {
 print(implode(PHP_EOL, $this-&gt;orders).PHP_EOL);
$shop = new CoffeeShop();
$shop-&gt;takeOrder("Cappuccino", 2);
$shop-&gt;takeOrder("Frappe", 1);
$shop-&gt;takeOrder("Espresso", 1);
$shop-&gt;takeOrder("Frappe", 897);
$shop-&gt;takeOrder("Cappuccino", 97);
$shop-&gt;takeOrder("Frappe", 3);
$shop-&gt;takeOrder("Espresso", 3);
$shop-&gt;takeOrder("Cappuccino", 3);
$shop-&gt;takeOrder("Espresso", 96);
$shop-&gt;takeOrder("Frappe", 552);
$shop-&gt;takeOrder("Cappuccino", 121);
$shop-&gt;takeOrder("Espresso", 121);
$shop-&gt;service();
print("CoffeeFlavor objects in cache: ".CoffeeFlavour::flavoursInCache().PHP_EOL);