享元模式
# 分类
结构型模式
# 定义
- 享元模式(Flyweight Pattern) 又译为
羽量级模式
或者绳量级模式
。采用一个共享类
来避免大量拥有相同内容的“小类”的开销。这种开销中最常见、直观的影响就是增加了内存的损耗。享元模式以共享
的方式高效的支持大量的细粒度对象,减少其带来的开销。
# 意图
- 事物之间都是不同的,但是又存在一定的共性,我们应该尽量将事物的
共性共享
,保留它的个性
。 - 运用
共享技术
有效地支持大量细粒度的对象。
什么样的细粒度对象通过共享可以节省资源使用呢?满足以下两条:
- 某个类型的对象实例众多
- 众多实例可以被有效地归为数量相对小得多的种类
# 应用场景
- 系统中有
大量的对象
,它们使系统效率降低。这些对象的状态可以分离出
所需要的内外
两部分。 - 如果一个应用程序使用了
大量的对象
,而大量的这些对象造成了很大的存储开销时
就应该考虑使用享元模式;还有就是对象的大多数状态可以是外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑使用享元模式。
应用实例
- 比如字符串对象
String
就用了享元模式。 围棋、五子棋、跳棋
之类的都可用享元模式。- 有大量
相似对象
,比如有一个包含多个属性
的对象,要被创建一百万次
,并使用他们,这时候是使用享元模式的好时机。 - 数据库的
数据池
对象池
、缓冲池
# 角色与结构图
单纯享元模式结构
- 抽象享元角色(Flyweight):为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式通过此方法传入。
- 具体享元角色(ConcreteFlyweight):实现抽象角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供存储空间。
- 享元工厂角色(FlyweightFactory):负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键。
- 客户端角色(Client):维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。
复合享元模式结构
- 抽象享元角色(Flyweight):为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式通过此方法传入。
- 具体享元角色(ConcreteFlyweight):实现抽象角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供存储空间。
- 复合享元角色(ConcreteCompositeFlyweight):它所代表的对象是不可以共享的,并且可以分解成为多个单纯享元对象的组合。
- 享元工厂角色(FlyweightFactory):负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键。
- 客户端角色(Client):维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。
下图解释了享元模式中各角色的作用


# 示例代码
//博客网站示例
//用户类
public class User{
private string name;
public User(String name){
this.name = name;
}
public String Name{
get{return this.name;}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
//网站抽象类
abstract class WebSite{
publlic abstract void Use(User user);
}
//网站具体类
class ConcreteWebSite : WebSite{
private string name = "";
public ConcreteWebSite(string name){
this.name = name;
}
public override void Use(User user){
Console.WriteLine("网站分类:"+ name + "用户:"+ user.Name);
}
}
//网站工厂类
class WebSiteFactory{
private Hashtable flyweights = new Hashtable();
//获得网站分类
public WebSite GetWebSiteCategory(string category){
if(!flyweights.ContainsKey(category)){
flyweights.Add(category, new ConcreteWebSite(category));
}
return ((WebSite)flyweights[category]);
}
//获得网站分类总数
public int GetWebSiteCount(){
return flyweights.Count;
}
}
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
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
// 客户端程序
class Client{
static void Main(string[] args) {
WebSiteFactory factory = new WebSiteFactory();
WebSite fx = factory.GetWebSiteCategory("产品展示");//共享部分
fx.Use(new User("小菜"));//差异部分
WebSite fy = factory.GetWebSiteCategory("产品展示");
fy.Use(new User("大鸟"));
WebSite fz = factory.GetWebSiteCategory("产品展示");
fy.Use(new User("姣姣"));
WebSite f1 = factory.GetWebSiteCategory("博客");
f1.Use(new User("老顽童"));
WebSite fm = factory.GetWebSiteCategory("博客");
fm.Use(new User("桃谷六仙"));
WebSite fn = factory.GetWebSiteCategory("博客");
fn.Use(new User("南海鳄神"));
Console.WriteLine("得到网站分类总数为{0}",factory.GetWebSiteCount());
Console.ReadLine();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 优点
- 大大减少对象的创建,降低系统的内存,使效率提高。
# 缺点
- 为了实现降低内存对象的数量使得系统逻辑复杂化。
- 外蕴状态影响了系统的速度。
- 需要维护一个记录了系统已有的所有享元的列表,这本身需要消耗资源。
# 小结
- 享元模式中区分了
内蕴状态
和外蕴状态
,内蕴状态就是共性
,外蕴状态就是个性
。- 内蕴状态存储在享元对象内部,不会随环境的改变而改变,是可以共享的。
- 外蕴状态是不可共享的,它随环境的改变而改变,因此
外蕴状态是由客户端
来保持(因为环境的变化是由客户端引起的)。 - 在每个具体的环境下,客户端将外蕴状态传递给享元,从而创建不同的对象出来。
- 享元模式分为
单纯享元模式
和复合享元模式
。 - 享元模式就是使用时间来换取空间的,可以采用相应的算法来提高查找速度。
- 享元模式可以避免大量非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的,有时就能大幅度地减少需要实例化的类的数量。如果能把那些
参数移到类实例的外面
,在方法调用时将他们传递进来
,就可以通过共享大幅度地减少单个实例的数目。也就是说,享元模式执行时所需的状态是有内部的也可能有外部的,内部存储于ConcreteFlyweight对象之中,而外部对象则应该考虑由客户端对象储存或计算,当调用Flyweight对象的操作时,将该状态传递给它。 - 因为用了享元模式,所以有了共享对象,实例总数就大大减少了,如果共享的对象越多,存储节约也就越多,节约量随着共享状态的增多而增大。
- 享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。
- 主要解决?在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
- 何时使用?1系统中有大量对象 2这些对象消耗大量内存 3这些对象的状态大部分可以外部化 4这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象可以用一个对象来替代。 5系统不依赖于这些对象身份,这些对象不可分辨。
- 关键代码?用
Hashtable
存储这些对象。 注意
划分外部状态和内部状态,否则可能会引起线程安全问题,这些必须有一个工厂对象
加以控制。
# 一句话概括
通过共享技术来有效的支持大量细粒度的对象。
编辑 (opens new window)
上次更新: 2025/03/22, 13:47:44