初识设计模式——亨元模式(Flyweight Pattern)

cuixiaogang

亨元模式(Flyweight Pattern),其运用共享技术有效的支持大量颗粒度的对象。亨元模式可以避免大量非常相似类的开销。

详解

在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的,那么把那些参数移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享大幅度地减少单个实例的数目。

亨元是什么意思

“亨元” 并非日常常见词汇,在亨元模式语境里,“亨” 有共享的含义,“元” 可理解为基本元素、单元。亨元模式里,“亨元” 代表可共享的细粒度对象,借助共享这些对象达成高效利用资源的目的。

亨元模式的构成

  • 抽象享元角色(Flyweight):此为所有具体享元类的抽象基类或者接口,规定了具体享元类需要实现的方法。
  • 具体享元角色(Concrete Flyweight):实现抽象享元角色所定义的方法,存储内部状态。内部状态指的是不依赖外部环境,可在不同上下文中共享的状态。
  • 非共享具体享元角色(Unshared Concrete Flyweight):并非所有享元模式都有此角色,它是不可共享的具体享元类,通常包含外部状态。外部状态是随环境变化而变化、不可共享的状态。
  • 享元工厂角色(Flyweight Factory):负责创建和管理享元对象,维护一个享元池(通常是一个集合),当请求一个享元对象时,先查看享元池是否存在该对象,若存在则直接返回,若不存在则创建并放入享元池。

享元模式结构图

示例

场景介绍

在一个电商网站中,商品分类信息是相对固定且会被频繁使用的。我们可以使用享元模式来管理商品分类对象,减少内存开销。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?php
// 抽象享元角色
interface CategoryFlyweight {
public function display($productName);
}

// 具体享元角色
class ConcreteCategoryFlyweight implements CategoryFlyweight {
private $categoryName;

public function __construct($categoryName) {
$this->categoryName = $categoryName;
}

public function display($productName) {
echo "Product: $productName, Category: $this->categoryName\n";
}
}

// 享元工厂角色
class CategoryFlyweightFactory {
private $flyweights = [];

public function getCategory($categoryName) {
if (!isset($this->flyweights[$categoryName])) {
$this->flyweights[$categoryName] = new ConcreteCategoryFlyweight($categoryName);
}
return $this->flyweights[$categoryName];
}

public function getFlyweightCount() {
return count($this->flyweights);
}
}

// 使用示例
$factory = new CategoryFlyweightFactory();

$product1 = $factory->getCategory('Electronics');
$product1->display('Smartphone');

$product2 = $factory->getCategory('Clothing');
$product2->display('T-Shirt');

$product3 = $factory->getCategory('Electronics');
$product3->display('Laptop');

echo "Total flyweights created: ". $factory->getFlyweightCount(). "\n";
?>

代码解释

在上述示例中,CategoryFlyweight 是抽象享元角色,ConcreteCategoryFlyweight 是具体享元角色,CategoryFlyweightFactory 是享元工厂角色。通过享元工厂来管理商品分类对象,当需要相同分类的对象时,直接从享元池中获取,避免了重复创建对象,从而减少了内存开销。

UML类图

UML类图

享元模式的优缺点

优点

  • 减少内存占用:通过共享对象,减少了系统中对象的数量,降低了内存开销。
  • 提高性能:减少了对象的创建和销毁过程,提高了系统的响应速度。

缺点

  • 增加系统复杂度:需要引入享元工厂来管理享元对象,增加了系统的复杂度。
  • 线程安全问题:如果多个线程同时访问和修改享元对象的外部状态,可能会出现线程安全问题。

享元模式的适用场景

  • 系统中存在大量相似对象:像游戏里的大量士兵、建筑等,这些对象很多属性是相同的,可通过享元模式共享相同属性,减少内存开销。
  • 需要缓冲池的场景:例如数据库连接池、线程池等,通过共享连接或线程对象,降低频繁创建和销毁对象的开销。
  • WEB 开发中的缓存:对于一些频繁使用且不常变化的数据,如菜单数据、配置信息等,可采用享元模式进行缓存,提升系统性能。