设计模式(11) 享元模式

基于面向对象思想设计的应用程序有时遇到需要场景大量相同或显示对象实例的场景,这些数量庞大的实例很可能会消耗很多系统资源,最直接的就是内存了。比如要一款围棋游戏,如果每次落子都新建一个对象,将会占用大量内存,而实际上棋子只有黑白两色,不同的只是落子位置而已。另外,大量的主动型对象还会占用很多CPU和显卡的计算资源,举个极端的例子,某个游戏的沙漠场景,为了使游戏具有丰富的视觉效果,要求每一粒沙子都要随着光线而有不同的呈现效果不同,这时候直接new当然也是不现实的。

享元模式

享元模式提供了一种针对这类场景的解决方案。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率,支持大量细粒度对象的复用。

UML类图:

代码实现

public abstract class Flyweight
{
    //内部状态
    public string Instrinsic { get; set; }
    //外部状态
    protected string Extrinsic { get; set; }

    public Flyweight(string extrinsic)
    {
        this.Extrinsic = extrinsic;
    }

    //定义业务操作
    public abstract void Operate(int id);
}

public class ConcreteFlyweight : Flyweight
{
    //接受外部状态
    public ConcreteFlyweight(String extrinsic) : base(extrinsic)
    {
    }

    //根据外部状态进行逻辑处理
    public override void Operate(int id)
    {
        Console.WriteLine("Flyweight:" + id);
    }
}

public class UnsharedConcreteFlyweight : Flyweight
{

    public UnsharedConcreteFlyweight(String extrinsic) : base(extrinsic)
    {
    }

    public override void Operate(int id)
    {
        Console.WriteLine("不共享的Flyweight:" + id);
    }
}

public class FlyweightFactory
{
    //定义一个池容器
    private static Dictionary<String, Flyweight> pool = new Dictionary<String, Flyweight>();

    //享元工厂
    public static Flyweight GetFlyweight(string extrinsic)
    {
        Flyweight flyweight = null;
        if (pool.ContainsKey(extrinsic))
        {
            flyweight = pool[extrinsic];
            Console.Write($"已有{extrinsic} ");
        }
        else
        {
            flyweight = new ConcreteFlyweight(extrinsic);
            pool.Add(extrinsic, flyweight);
            Console.Write($"新建{extrinsic} ");
        }
        return flyweight;
    }
}

享元模式的核心是用一个池容器来缓存需要共享的对象,C#可以用Dictionary来实现。

内部状态与外部状态
由于这些数量较大的细粒度对象有着相近的性质,为了能共享这些对象,需要将这些对象的信息分为两个部分:内部状态和外部状态。

  • 内部状态指对象共享出来的信息,存储在享元对象内部并且不会随环境的改变而改变;
  • 外部状态指会随环境改变而改变的、不可共享的状态。
    比如前面围棋的例子中,棋子的黑白两色就可作为内部状态,落子位置则是外部状态,将内部状态(黑、白两色)作为对象间的本质区别,只需要两个对象就可以了,然后配合外部状态(落子位置)的变化,就可以表示全部的棋子。

围棋例子的误导
使用围棋这个例子会容易让人产生一个困惑:既然实际上只有两个对象(黑、白),那么是如何让同一个对象即出现在位置A,又出现在位置B的呢?难不成像薛定谔的猫那样,可以有多种状态?
实际上这里所谓的共享对象,共享的应该是对象的行为。在围棋游戏中可以理解为,在画布上绘制的棋子图案。每个棋子对象都有个绘制棋子图案的行为,通过设置不同的外部状态(落子位置),就棋子的绘图行为就会在画布上不同的位置绘制图案。

享元模式的适用场景

  • 系统中有大量对象。
  • 这些对象消耗大量内存。
  • 这些对象的状态大部分可以外部化。
  • 这些对象可以按照内部状态分为很多组,当把外部对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。
  • 系统不依赖于这些对象的身份,这些对象是不可分辨的。

享元模式的优缺点

优点

  • 大大减少对象的创建,降低系统的资源占用,提高效率。
  • 由于抽离出了外部状态和内部状态,外部状态相对独立,不会影响到内部状态,所以享元模式使得享元对象能够在不同的环境被共享。
    缺点
  • 增加了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。
  • 为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。

参考书籍:
王翔著 《设计模式——基于C#的工程化实现及扩展》

(0)

相关推荐

  • 结构型模式之享元模式

    在面向对象程序设计过程中,有时会面临要创建大量相同或相似对象实例的问题.创建那么多的对象将会耗费很多的系统资源,它是系统性能提高的一个瓶颈.例如,围棋和五子棋中的黑白棋子,图像中的坐标点或颜色,局域网 ...

  • Flyweight享元模式

    >>返回<C#常用设计模式> 1. 简介 2. 示例 1. 简介 定义 使用共享对象可有效地支持大量的细粒度的对象. 解决问题 面向对象技术可以很好地解决一些灵活性或可扩展性问 ...

  • 设计模式-享元模式

    定义 运用共享技术有效地支持大量细粒度的对象. 适用场景 例如,数据库连接,线程的创建开销都比较大,并且创建频率也非常高,因此就需要用到数据库连接池技术和线程池技术来共享数据库连接和线程.再例如,应用 ...

  • PHP设计模式之享元模式

    PHP设计模式之享元模式 享元模式,"享元"这两个字在中文里其实并没有什么特殊的意思,所以我们要把它拆分来看."享"就是共享,"元"就是元素 ...

  • [PHP小课堂]PHP设计模式之享元模式

    [PHP小课堂]PHP设计模式之享元模式 关注公众号:[硬核项目经理]获取最新文章 添加微信/QQ好友:[DarkMatterZyCoder/149844827]免费得PHP.项目管理学习资料

  • 设计模式之享元模式

    享元模式 Flyweight Intro 享元是指一个可复用的对象,通过复用这个享元来减少应用中的内存分配. 享元模式是为了减少内存占用,尽可能复用已有对象的设计模式,一般来说会把这个可复用的对象放到 ...

  • 无废话设计模式(9)结构型模式--享元模式

    0-前言 享元模式定义:运用共享技术有效地支持大量细粒度的对象. 1-实现 1-1.简单UML图:  1-2.代码实现 //1.抽象父类(网站父类) abstract class Website { ...

  • 设计模式 --面试高频之享元模式

    前言 享元模式是非常常用的一种结构性设计模式. 特别是在面试的时候.当我们把这一节内容掌握,我相信不管是工作中还是面试中这一块内容绝对是一大亮点. 什么是享元模式 所谓"享元",顾 ...

  • 泡图书馆,我想到了 享元模式

    回复"000"获取程序员必备电子书 大家好,我是老田,今天我给大家分享设计模式中的享元模式.用贴切的生活故事,以及真实项目场景来讲设计模式,最后用一句话来总结这个设计模式. 另外, ...