(11)结构型模式——享元

匆匆过客 提交于 2020-02-05 22:08:07

结构型模式——享元(Flyweight)

问题背景

当使用大量细粒度对象,需要提高系统性能时,考虑使用享元。现在我们要为一个RPG游戏设计装备系统,首先根据需求提取出装备类的属性,可能包括:标识符、名称、描述、装备类型、初始属性取值集合、穿戴条件、属性集合、耐久上限、当前耐久、最高强化等级、强化等级……我们会发现这些属性中,有些在同种装备中都是相同的,并且在运行时不会改变,比如名称、描述、初始属性取值集合、最高强化等级,我们把这些属性称为“静态属性”;还有一些是每个装备实例都不同的,在运行时会改变,比如属性集合、当前耐久、强化等级,我们把这些属性称为“动态属性”。

如果将静态属性和动态属性放在同一个类里,一方面会导致每个实例都维护大量相同数据,严重影响程序的性能;另一方面无法显示地区分静态属性和动态属性,容易造成混淆,尤其是这种涉及到类(class)和分类(category)两种相似概念的系统。

解决方案

把静态属性和动态属性放在同一个类中会导致运行性能低下,系统难以理解。下面使用享元,将静态属性和动态属性拆开。

增加一个类EquipmentFlyweight来存放静态属性,原本的Equipment类中的静态属性改为对EquipmentFlyweight的引用。如此一来,同种装备就会共享同一个Flyweight对象,大大降低了内存开销。比如:铜剑共享铜剑的静态属性、铁剑共享铁剑的静态属性。使用享元后的程序结构是这样的:
程序结构

效果

  1. 将共享数据的备份减少至一份,降低了内存开销。
  2. 一般情况下,引入这种无法与现实事物对应的抽象概念会降低系统的可理解性,而享元模式恰恰相反。

缺陷

应该保证每个种类的享元只有一个实例,那么如何实现呢?使用单例?不不不,享元的个数往往是动态变化的,不能写死在代码里。使用工厂?那要怎么保证用户不会越过工厂自己去创建享元?唯一可行的方案就是让享元类同时作为自己的工厂,并维护一个已有享元的集合。但这种做法又破坏了单一职责原则,使得享元类拥有了两个职责。

实际上,任何纯OO的手段都无法很好地实现上述效果。在第二期中会介绍的混合了反射机制的享元模式,可以有较好的表现。

相关模式

  1. 复合:复合结构中的某些叶节点可以使用享元。
  2. 状态、策略:这二者一般用享元实现。

实现

using System;

namespace Flyweight
{
    class Client
    {
        public class Equipment
        {
            public long Id { get; set; }
            public int Strength { get; set; }
            public int EnhancementLevel { get; set; }
            public EquipmentFlyweight Flyweight { get; }
            public Equipment(long id, int strength, int elevel, EquipmentFlyweight flyweight)
            {
                Id = id;
                Strength = strength;
                EnhancementLevel = elevel;
                Flyweight = flyweight;
            }
            public void Show()
            {
                Console.WriteLine($"{Flyweight.Name}({Id})");
                Console.WriteLine(Flyweight.Desc);
                Console.WriteLine(Flyweight.Etype);
                Console.WriteLine(Flyweight.Econdition);
                Console.WriteLine($"耐久度: {Strength} / {Flyweight.StrengthLimit}");
                Console.WriteLine($"强化等级: {EnhancementLevel} / {Flyweight.EnhancementLimit}");
            }
        }

        public class EquipmentFlyweight
        {
            public string Name { get; }
            public string Desc { get; }
            public string Etype { get; }
            public string Econdition { get; }
            public int StrengthLimit { get; }
            public int EnhancementLimit { get; }
            public EquipmentFlyweight(string name,
                                      string desc,
                                      string etype,
                                      string econdition,
                                      int slimit,
                                      int elimit)
            {
                Name = name;
                Desc = desc;
                Etype = etype;
                Econdition = econdition;
                StrengthLimit = slimit;
                EnhancementLimit = elimit;
            }
        }

        static void Main(string[] args)
        {
            Console.WriteLine("创建享元...");
            var swordf = new EquipmentFlyweight("长剑", "40米长", "武器", "10级", 1000, 5);
            var shieldf = new EquipmentFlyweight("圆盾", "阿尔托莉雅的小饭桌", "武器", "女用", 2000, 10);

            Console.WriteLine("创建一把长剑...");
            var sword1 = new Equipment(10010, 700, 4, swordf);
            sword1.Show();

            Console.WriteLine("创建一面圆盾...");
            var shield = new Equipment(10086, 1500, 8, shieldf);
            shield.Show();

            Console.WriteLine("再来一把剑...");
            var sword2 = new Equipment(11111, 900, 5, swordf);
            sword2.Show();
        }
    }
}

运行结果

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!