热门问题
时间线
聊天
视角
享元模式
来自维基百科,自由的百科全书
Remove ads
享元模式(英語:Flyweight Pattern)是一種軟體設計模式[1]。它使用物件用來儘可能減少記憶體使用量;於相似物件中分享儘可能多的資訊。當大量物件近乎重複方式存在,因而使用大量記憶體時,此法適用。通常物件中的部分狀態(state)能夠共享。常見做法是把它們放在資料結構外部,當需要使用時再將它們傳遞給享元。

概述

典型的享元模式的例子,是文書處理器中用來表示字符(character,亦譯為「字元」)的數據結構。在樸素的觀念上,文檔中的每個字符都有一個字形(glyph)物件,它含有字体(typeface或font family)、字型(font或font face)的重量与大小(比如点数)和其它格式資訊,但這會使得為每個字符都耗用成百上千個位元組。取而代之的是,每個字符參照到一個共享字形物件,此物件會被其它有共同特質的字符所分享;只有每個字符(文件中或頁面中)的位置才需要另外儲存。
享元对象可以做到[2]:
- 存储内在(intrinsic)状态,它是不变化的、上下文无关的并且可共享的,例如在一个给定的字符集中的字符
A
的代码。 - 提供一个接口用来传入外在(extrinsic)状态,它是变化的、上下文依赖的并且不能被共享的,例如在一个文本文档中字符
A
的位置。
客户可以重复使用Flyweight
对象并在需要时传入外在状态,减小免在物理上创建的对象的数目。
Remove ads
结构

Client
类,它使用享元模式。FlyweightFactory
类,它是创建和共享Flyweight
对象的工厂。Flyweight
接口,接入外在状态并在其上进行运算。Flyweight1
类,它实现Flyweight
并存储内在状态。
上面右侧的序列图展示了运行时交互:
Client
对象在FlyweightFactory
之上调用其getFlyweight(key)
,FlyweightFactory
返回一个Flyweight1
对象。Client
在返回的Flyweight1
对象之上调用其operation(extrinsicState)
。Client
再次在FlyweightFactory
之上调用其getFlyweight(key)
,FlyweightFactory
返回已经存在了的Flyweight1
对象。
示例
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] = ®istry.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程式用來解釋上述的文字。這個例子用來解釋享元模式利用只加載执行任務时所必需的最少資料,因而減少記憶體使用量。
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的例子:
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
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
参见
引用
外部連結
Wikiwand - on
Seamless Wikipedia browsing. On steroids.
Remove ads