享元模式

在计算机编程中，享元模式（），是一種軟體設計模式。它使用物件用來儘可能減少記憶體使用量；於相似物件中分享儘可能多的資訊。當大量物件近乎重複方式存在，因而使用大量記憶體時，此法適用。通常物件中的部分能夠共享。常見做法是把它們放在資料結構外部，當需要使用時再將它們傳遞給享元。

概述
alt=LibreOffice Writer的截屏。|thumb|400px|文本编辑器比如LibreOffice Writer，经常使用享元模式。
典型的享元模式的例子，是文書處理器中用來表示字符（character，亦譯為「字元」）的數據結構。在樸素的觀念上，文檔中的每個字符都有一個字形（glyph）物件，它含有字体（typeface或font family）、字型（font或font face）的重量与大小（比如点数）和其它格式資訊，但這會使得為每個字符都耗用成百上千個位元組。取而代之的是，每個字符參照到一個共享字形物件，此物件會被其它有共同特質的字符所分享；只有每個字符（文件中或頁面中）的位置才需要另外儲存。

享元对象可以做到：
 存储（intrinsic），它是不变化的、上下文无关的并且可共享的，例如在一个给定的字符集中的字符A的代码。
 提供一个接口用来传入（extrinsic）状态，它是变化的、上下文依赖的并且不能被共享的，例如在一个文本文档中字符A的位置。

客户可以重复使用Flyweight对象并在需要时传入外在状态，减小免在物理上创建的对象的数目。

 结构 
上面左侧的UML类图展示了：
 Client类，它使用享元模式。
 FlyweightFactory类，它是创建和共享Flyweight对象的工厂。
 Flyweight接口，接入外在状态并在其上进行运算。
 Flyweight1类，它实现Flyweight并存储内在状态。

上面右侧的序列图展示了运行时交互：
 Client对象在FlyweightFactory之上调用其getFlyweight(key)，FlyweightFactory返回一个Flyweight1对象。
 Client在返回的Flyweight1对象之上调用其operation(extrinsicState)。
 Client再次在FlyweightFactory之上调用其getFlyweight(key)，FlyweightFactory返回已经存在了的Flyweight1对象。

 示例 
 C++ 
C++标准模板库提供了允许从键映射到唯一性对象的多种容器。使用了这种容器，通过移除创建临时对象的需要，能有助于减少内存使用。

import std;

template <typename K, typename V>
using TreeMap = std::map<K, V>;
template <typename K, typename V>
using HashMap = std::unordered_map<K, V>;

// 租户类Tenant的实例是享元
class Tenant {
private:
    const std::string name;
public:
    Tenant(std::string_view name): 
        name{name} {}

    [[nodiscard]]
    std::string getName() const noexcept {
        return name;
    }
};

// 注册类Registry充当Tenant享元对象的工厂
// 并在租户容器中将租户名字映射到唯一性的租户对象
class Registry {
private:
    HashMap<std::string, Tenant> tenants;
public:
    Registry() = default;

    [[nodiscard]]
    Tenant& findByName(std::string_view name) {
        if (!tenants.contains(name)) {
            tenants[name] = Tenant{name};
        }
        return tenants[name];
    }
};

// 公寓类Apartment在房客容器中将房间编号映射到唯一性的租户对象
class Apartment {
private:
    TreeMap<int, Tenant*> occupants;
    Registry registry;
public:
    Apartment() = default;

    void addOccupant(std::string_view name, int room) {
        occupants[room] = &registry.findByName(name);
    }

    void printTenants() {
        for (const std::pair<int, Tenant>& i : occupants) {
            const int& room = i.first;
            const Tenant& 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<FontData, WeakReference<FontData>> FLY_WEIGHT_DATA =
        new WeakHashMap<FontData, WeakReference<FontData>>();
    private final int pointSize;
    private final String fontFace;
    private final Color color;
    private final Set<FontEffect> effects;

    private FontData(int pointSize, String fontFace, Color color, EnumSet<FontEffect> 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<FontEffect> 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<FontData> ref = FLY_WEIGHT_DATA.get(data);
        FontData result = (ref != null) ? ref.get() : null;

        // 如果检索结果没有匹配的实例存在，在缓存中存储新的FontData实例。
        if (result == null) {
            FLY_WEIGHT_DATA.put(data, new WeakReference<FontData> (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 && other.fontFace.equals(fontFace)
                && other.color.equals(color) && 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
import functools

class invoke_from_class():
    def __init__(self, fn):
        self.fn = fn
        functools.update_wrapper(self, fn)
    def __get__(self, obj, objtype=None):
        self.from_class = True if obj is None else False
        return self
    def __call__(self, *args, **kwargs):
        if self.from_class is True:
            return self.fn(*args, **kwargs)

class Multiton():
    shareable_dict = WeakValueDictionary()
    def __new__(cls, **kwargs):
        key = tuple(f"{v}" for k, v in kwargs.items())
        if key in cls.shareable_dict:
            obj = cls.shareable_dict[key]
        else:
            obj = super().__new__(cls)
            cls.shareable_dict[key] = obj 
            cls.__init__(obj, **kwargs)
        return obj
    @invoke_from_class
    def __init__(self, *args, **kwargs):
        for key, value in kwargs.items():
            self.__dict__[key] = value

all_font_style = {'Regular', 'Bold', 'Italic',
    'Superscript', 'Subscript', 'Strikethrough'}

class GlyphFactory(Multiton):
    def __new__(cls, glyph, font_family,
            font_size, font_style={'Regular'}):
        assert len(glyph) <= 1
        assert font_style <= all_font_style
        obj = super().__new__(cls, 
            glyph=glyph, font_family=font_family, 
            font_size=font_size, font_style=font_style)
        return obj

class Glyph(ABC):
    def __len__(self):
        return len(self.glyph)
    def __getitem__(self, index):
        return self.glyph[index]
    def __setitem__(self, index, value):
        self.glyph[index] = value
    def __delitem__(self, index):
        del self.glyph[index]
    @abstractmethod 
    def draw(self, text_color='Automatic'): pass

class Character(Glyph, GlyphFactory):
    def __repr__(self):
        return ', '.join(f"{k}={v!r}" 
            for k, v in self.__dict__.items())
    def draw(self, text_color='Automatic'): pass

class Paragraph(Glyph):
    def __init__(self, string, font_family, font_size,
            font_style={'Regular'}, *args, **kwargs):
        self.font_family = font_family
        self.font_size = font_size
        self.font_style = font_style
        self.glyph = [Character(char,
            font_family, font_size, font_style)
            for char in string]
    def draw(self, *args, **kwargs):
        for c in self.glyph:
            c.draw(*args, **kwargs)
这里在“蝇量”模式中将实现对象共享的明确表达了出来。下面是其执行：
>>> char1 = Character('A', 'Noto Serif CJK', 24)
>>> char1
glyph='A', font_family='Noto Serif CJK', font_size=24, font_style={'Regular'}
>>> char2 = Character('A', 'Noto Serif CJK', 14, {'Bold'})
>>> char2
glyph='A', font_family='Noto Serif CJK', font_size=14, font_style={'Bold'}
>>> char3 = Character('A', 'Noto Serif CJK', 24)
>>> assert char3 is char1
>>> para = Paragraph('ABC DEF', 'Noto Serif CJK', 14)
>>> para[0]
glyph='A', font_family='Noto Serif CJK', font_size=14, font_style={'Regular'}

参见
弱引用
记忆化

引用

外部連結
UML 和 LePUS3 （一種形式塑模語言）中的享元模式
"使你的程式飛起來 - 實作享元以改善效能 "
"享元模式"
結構模式 - 享元模式

Category:軟體設計模式