享元模式

# 分类

结构型模式

# 定义

  • 享元模式(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
//网站抽象类
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
// 客户端程序
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

# 优点

  • 大大减少对象的创建,降低系统的内存,使效率提高。

# 缺点

  • 为了实现降低内存对象的数量使得系统逻辑复杂化。
  • 外蕴状态影响了系统的速度。
  • 需要维护一个记录了系统已有的所有享元的列表,这本身需要消耗资源。

# 小结

  • 享元模式中区分了内蕴状态外蕴状态内蕴状态就是共性外蕴状态就是个性
    • 内蕴状态存储在享元对象内部,不会随环境的改变而改变,是可以共享的。
    • 外蕴状态是不可共享的,它随环境的改变而改变,因此外蕴状态是由客户端来保持(因为环境的变化是由客户端引起的)。
    • 在每个具体的环境下,客户端将外蕴状态传递给享元,从而创建不同的对象出来。
  • 享元模式分为单纯享元模式复合享元模式
  • 享元模式就是使用时间来换取空间的,可以采用相应的算法来提高查找速度。
  • 享元模式可以避免大量非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的,有时就能大幅度地减少需要实例化的类的数量。如果能把那些参数移到类实例的外面,在方法调用时将他们传递进来,就可以通过共享大幅度地减少单个实例的数目。也就是说,享元模式执行时所需的状态是有内部的也可能有外部的,内部存储于ConcreteFlyweight对象之中,而外部对象则应该考虑由客户端对象储存或计算,当调用Flyweight对象的操作时,将该状态传递给它。
  • 因为用了享元模式,所以有了共享对象,实例总数就大大减少了,如果共享的对象越多,存储节约也就越多,节约量随着共享状态的增多而增大。
  • 享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。
  • 主要解决?在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
  • 何时使用?1系统中有大量对象 2这些对象消耗大量内存 3这些对象的状态大部分可以外部化 4这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象可以用一个对象来替代。 5系统不依赖于这些对象身份,这些对象不可分辨。
  • 关键代码?用Hashtable存储这些对象。
  • 注意划分外部状态和内部状态,否则可能会引起线程安全问题,这些必须有一个工厂对象加以控制。

# 一句话概括

通过共享技术来有效的支持大量细粒度的对象。

上次更新: 2025/03/22, 13:47:44
最近更新
01
Git问题集合
01-29
02
安装 Nginx 服务器
01-25
03
安装 Docker 容器
01-25
更多文章>
×
×