结构型模式——享元(Flyweight)
问题背景
当使用大量细粒度对象,需要提高系统性能时,考虑使用享元。现在我们要为一个RPG游戏设计装备系统,首先根据需求提取出装备类的属性,可能包括:标识符、名称、描述、装备类型、初始属性取值集合、穿戴条件、属性集合、耐久上限、当前耐久、最高强化等级、强化等级……我们会发现这些属性中,有些在同种装备中都是相同的,并且在运行时不会改变,比如名称、描述、初始属性取值集合、最高强化等级,我们把这些属性称为“静态属性”;还有一些是每个装备实例都不同的,在运行时会改变,比如属性集合、当前耐久、强化等级,我们把这些属性称为“动态属性”。
如果将静态属性和动态属性放在同一个类里,一方面会导致每个实例都维护大量相同数据,严重影响程序的性能;另一方面无法显示地区分静态属性和动态属性,容易造成混淆,尤其是这种涉及到类(class)和分类(category)两种相似概念的系统。
解决方案
把静态属性和动态属性放在同一个类中会导致运行性能低下,系统难以理解。下面使用享元,将静态属性和动态属性拆开。
增加一个类EquipmentFlyweight来存放静态属性,原本的Equipment类中的静态属性改为对EquipmentFlyweight的引用。如此一来,同种装备就会共享同一个Flyweight对象,大大降低了内存开销。比如:铜剑共享铜剑的静态属性、铁剑共享铁剑的静态属性。使用享元后的程序结构是这样的:
效果
- 将共享数据的备份减少至一份,降低了内存开销。
- 一般情况下,引入这种无法与现实事物对应的抽象概念会降低系统的可理解性,而享元模式恰恰相反。
缺陷
应该保证每个种类的享元只有一个实例,那么如何实现呢?使用单例?不不不,享元的个数往往是动态变化的,不能写死在代码里。使用工厂?那要怎么保证用户不会越过工厂自己去创建享元?唯一可行的方案就是让享元类同时作为自己的工厂,并维护一个已有享元的集合。但这种做法又破坏了单一职责原则,使得享元类拥有了两个职责。
实际上,任何纯OO的手段都无法很好地实现上述效果。在第二期中会介绍的混合了反射机制的享元模式,可以有较好的表现。
相关模式
- 复合:复合结构中的某些叶节点可以使用享元。
- 状态、策略:这二者一般用享元实现。
实现
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();
}
}
}
来源:CSDN
作者:DiaX
链接:https://blog.csdn.net/DIAX_/article/details/104186709