• 设计模式的征途—12.享元(Flyweight)模式


    现在在大力推行节约型社会,“浪费可耻,节俭光荣”。在软件系统中,有时候也会存在资源浪费的情况,例如,在计算机内存中存储了多个完全相同或者非常相似的对象,如果这些对象的数量太多将导致系统运行代价过高。那么,是否存在一种技术可以用于节约内存使用空间,实现对这些相同或者相似对象的共享访问呢?答案是肯定的,这种技术就是享元模式。

    享元模式(Flyweight) 学习难度:★★★★☆ 使用频率:★☆☆☆☆

    一、围棋棋子的设计

    M公司开发部欲开发一个围棋软件,其界面效果如下图所示:

      M公司开发人员通过对围棋软件进行分析,发现在围棋棋盘中包含大量的黑子和白子,它们的形状、大小都一模一样,只是出现的位置不同而已。如果将每一个棋子都作为一个独立的对象存储在内存中,将可能导致该围棋软件在运行时所需要的内存空间较大。

      如何降低运行代价、提高系统性能是M公司开发人员需要解决的一个问题。为此,M公司开发人员决定使用享元模式来设计该软件。

    二、享元模式概述

    2.1 享元模式简介

    享元(Flyweight)模式:运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,是一种结构型模式。

    2.2 享元模式结构

      享元模式的结构较为复杂,一般结合工厂模式一起使用,在其结构图中包含了一个享元工厂类,如下图所示:

      由上图可以看出,它包含了以下4个角色:

      (1)Flyweight(抽象享元类):一个接口或抽象类,声明了具体享元类的公共方法。

      (2)ConcreteFlyweight(具体享元类):实现了抽象享元类,其实例称为享元对象。

      (3)UnsharedConcreteFlyweight(非共享具体享元类):并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为费共享具体享元类。

      (4)FlyweightFactory(享元工厂类):用于创建并管理享元对象,一般设计为一个存储“Key-Value”键值对的集合(可以结合工厂模式设计)。其作用就在于:提供一个用于存储享元对象的享元池,当用户需要对象时,首先从享元池中获取,如果享元池中不存在,那么则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。=> 想想.NET中的各种资源池的设计?

    三、重构后的围棋棋子方案

    3.1 重构后的设计

      M公司开发人员采用了享元模式进行设计,如下图所示:

      其中,IgoChessman充当抽象享元类,BlackIgoChessman和WhiteIgoChessman充当具体享元类,IgoChessmanFactory充当享元工厂类。

    3.2 具体代码实现

      (1)抽象享元类:IgoChessman

        public abstract class IgoChessman
        {
            public abstract string GetColor();
    
            public void Display(Coordinates coord)
            {
                Console.WriteLine("棋子颜色:{0},棋子位置:{1}", GetColor(), coord.X + "," + coord.Y);
            }
        }
    
        /// <summary>
        /// 外部状态:棋子坐标
        /// </summary>
        public class Coordinates
        {
            public int X { get; set; }
            public int Y { get; set; }
    
            public Coordinates()
            {
    
            }
    
            public Coordinates(int x, int y)
            {
                this.X = x;
                this.Y = y;
            }
        }

      (2)具体享元类:BlackIgoChessman、WhiteIgoChessman

        // 具体享元类A
        public class BlackIgoChessman : IgoChessman
        {
            public override string GetColor()
            {
                return "黑色";
            }
        }
    
        // 具体享元类B
        public class WhiteIgoChessman : IgoChessman
        {
            public override string GetColor()
            {
                return "白色";
            }
        }

      (3)享元工厂类:IgoChessmanFactory

        /// <summary>
        /// 享元工厂类
        /// </summary>
        public class IgoChessmanFactory
        {
            private static readonly IgoChessmanFactory instance = new IgoChessmanFactory(); // 使用单例模式实现享元
            private static Hashtable ht;    // 使用Hashtable来存储享元对象,充当享元池
    
            private IgoChessmanFactory()
            {
                ht = new Hashtable();
                IgoChessman blackChess = new BlackIgoChessman();
                ht.Add("b", blackChess);
                IgoChessman whiteChess = new WhiteIgoChessman();
                ht.Add("w", whiteChess);
            }
    
            public static IgoChessmanFactory GetInstance()
            {
                return instance;
            }
    
            public IgoChessman GetIgoChessman(string color)
            {
                IgoChessman chess = ht[color] as IgoChessman;
                return chess;
            }
        }

      (4)客户端调用

        public class Program
        {
            public static void Main(string[] args)
            {
                // 获取享元工厂
                IgoChessmanFactory chessFactory = IgoChessmanFactory.GetInstance();
                // 通过享元工厂获取3颗黑子
                IgoChessman blackChess1 = chessFactory.GetIgoChessman("b");
                IgoChessman blackChess2 = chessFactory.GetIgoChessman("b");
                IgoChessman blackChess3 = chessFactory.GetIgoChessman("b");
    
                Console.WriteLine("判断两颗黑子是否相同:{0}", object.ReferenceEquals(blackChess1, blackChess2));
                // 通过享元工厂获取2颗白子
                IgoChessman whiteChess1 = chessFactory.GetIgoChessman("w");
                IgoChessman whiteChess2 = chessFactory.GetIgoChessman("w");
    
                Console.WriteLine("判断两颗白子是否相同:{0}", object.ReferenceEquals(whiteChess1, whiteChess2));
                // 显示棋子
                blackChess1.Display(new Coordinates(1,2));
                blackChess2.Display(new Coordinates(3, 4));
                blackChess3.Display(new Coordinates(1, 3));
                whiteChess1.Display(new Coordinates(2, 5));
                whiteChess2.Display(new Coordinates(2, 4));
    
                Console.ReadKey();
            }
        }

      运行结果如下图所示:

      

      从运行结果可以看出,在每次调用Display()方法时,都设置了不同的外部状态-坐标值,因此相同的棋子虽然具有相同的颜色,但是它们的坐标值不同,将显示在棋盘的不同位置。

    四、享元模式总结

    4.1 主要优点

      可以极大减少内存中对象的数量,使得相同或相似对象在内存中只有一份 => 节省系统资源,提高系统性能!棒棒哒!

    4.2 主要缺点

      为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长!

    4.3 应用场景

      (1)一个系统有大量相同或相似的对象,造成了系统内存的大量损耗 => 赶紧使用享元模式吧!

      (2)对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。

      (3)要维护享元模式,需要耗费一定的系统资源,因为在需要时会多次重复使用才值得使用享元模式了!

    参考资料

      DesignPattern

      刘伟,《设计模式的艺术—软件开发人员内功修炼之道》

  • 相关阅读:
    JavaScript + Table 系列:排序
    asp.net 2.0中傻瓜式使用soap header
    Linq To Sql进阶系列(七)动态查询续及CLR与SQL在某些细节上的差别
    asp.net 2.0 导出DataTable到Excel中
    ASP.NET实用技巧(一)
    【ASP.NET】基础补习之FileUpload
    ASP.NET AJAX入门系列(8):自定义异常处理
    Linq To Sql进阶系列(五)Store Procedure篇
    Linq To Sql进阶系列(六)用object的动态查询与保存log篇
    深究“通过样式表实现固定表头和列”
  • 原文地址:https://www.cnblogs.com/edisonchou/p/7148258.html
Copyright © 2020-2023  润新知