热门问题
时间线
聊天
视角

享元模式

来自维基百科,自由的百科全书

Remove ads

享元模式(英語:Flyweight Pattern)是一種軟體設計模式[1]。它使用物件用來儘可能減少記憶體使用量;於相似物件中分享儘可能多的資訊。當大量物件近乎重複方式存在,因而使用大量記憶體時,此法適用。通常物件中的部分狀態(state)能夠共享。常見做法是把它們放在資料結構外部,當需要使用時再將它們傳遞給享元。

Thumb
享元模式的UML类图。

概述

Thumb
文本编辑器比如LibreOffice Writer,经常使用享元模式。

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

享元对象可以做到[2]

  • 存储内在英语Intrinsic and extrinsic properties(intrinsic)状态,它是不变化的、上下文无关的并且可共享的,例如在一个给定的字符集中的字符A的代码。
  • 提供一个接口用来传入外在英语Intrinsic and extrinsic properties(extrinsic)状态,它是变化的、上下文依赖的并且不能被共享的,例如在一个文本文档中字符A的位置。

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

Remove ads

结构

Thumb
享元模式的样例UML类图和序列图[3]

上面左侧的UML类图展示了:

  • Client类,它使用享元模式。
  • FlyweightFactory类,它是创建和共享Flyweight对象的工厂
  • Flyweight接口,接入外在状态并在其上进行运算。
  • Flyweight1类,它实现Flyweight并存储内在状态。

上面右侧的序列图展示了运行时交互:

  1. Client对象在FlyweightFactory之上调用其getFlyweight(key)FlyweightFactory返回一个Flyweight1对象。
  2. Client在返回的Flyweight1对象之上调用其operation(extrinsicState)
  3. 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;
}
Remove ads

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();
    }
}
Remove ads

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',
    'Superscript', 'Subscript', 'Strikethrough'}

class GlyphFactory():
    sharable_dict = WeakValueDictionary()
    def __new__(cls, char, font_family,
            font_size, font_style={'Regular'}):
        assert len(char) == 1
        assert font_style <= 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写的类似例子的区别在于,它的目标是共享而非推延,下面是其执行:

>>> char1 = Character('A', 'Noto Serif CJK', 24)
>>> char1.info()
"Character: A, FontFamily: Noto Serif CJK, FontSize: 24, FontStyle: {'Regular'}"
>>> char2 = Character('A', 'Noto Serif CJK', 14, {'Bold'})
>>> char2.info()
"Character: A, FontFamily: Noto Serif CJK, FontSize: 14, FontStyle: {'Bold'}"
>>> char3 = Character('A', 'Noto Serif CJK', 24)
>>> assert char3 is char1
>>>
Remove ads

PHP

下面是PHP的例子:

<?php
class CoffeeFlavour {
    private static array $CACHE = [];
    private function __construct(private string $name) {}

    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->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->flavour} to table {$this->tableNumber}";
    }
}

class CoffeeShop {
    private array $orders = [];

    public function takeOrder(string $flavour, int $tableNumber) {
        $this->orders[] = Order::create($flavour, $tableNumber);
    }

    public function service() {
        print(implode(PHP_EOL, $this->orders).PHP_EOL);
    }
}

$shop = new CoffeeShop();
$shop->takeOrder("Cappuccino", 2);
$shop->takeOrder("Frappe", 1);
$shop->takeOrder("Espresso", 1);
$shop->takeOrder("Frappe", 897);
$shop->takeOrder("Cappuccino", 97);
$shop->takeOrder("Frappe", 3);
$shop->takeOrder("Espresso", 3);
$shop->takeOrder("Cappuccino", 3);
$shop->takeOrder("Espresso", 96);
$shop->takeOrder("Frappe", 552);
$shop->takeOrder("Cappuccino", 121);
$shop->takeOrder("Espresso", 121);
$shop->service();
print("CoffeeFlavor objects in cache: ".CoffeeFlavour::flavoursInCache().PHP_EOL);
?>
Remove ads

参见

引用

外部連結

Loading related searches...

Wikiwand - on

Seamless Wikipedia browsing. On steroids.

Remove ads