小话设计模式(十一)享元模式

享元(Fly Weight直译作蝇量)模式运用共享技术有效的支持大量细颗粒的对象。

一般使用享元模式的时候要满足下列所有情况:

1、 一个应用程序使用了大量的对象。

2、 完全由于使用大量的对象,造成了很大的存储开销。

3、 对象的大多数状态都可以变外外部状态。

4、 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。

5、 应用程序不依赖与对象标识。

举个例子

一个RPG游戏,怪兽或者英雄都可以改变等级,改变等级之后,二者的攻击力都会发生变化。我们将等级数据(每一级对应的攻击力)存放在本地数据文件里。当我们需要创建一个对象的时候,就要获取这些等级数据。

游戏里的等级数据定义如下:

 

public abstract class LevelData
{
	protected virtual void LoadFromFile (string filePath)
	{
		//TODO:
	}
	public abstract int GetAttack (ClientData data);
}

这些数据都是从本地数据文件中读取出来的,这些数据对于每个怪兽都是一样的,所以是共享数据。而且,这些数据是不会变的,除非修改数据文件。

这里提供了一个GetAttack的公开方法,用于获取(怪兽、英雄)攻击力

怪兽等级数据:

 

public class MonsterLevelData : LevelData
{
	public int ID { get; private set;}
	public MonsterLevelData(int monID)
	{
		ID = monID;
		//For different heroes, load different files.
		LoadFromFile (monID.ToString());
	}

	private List<int> _levelAttackList = new List<int> ();//Loaded in the method LoadFromFile
	protected override void LoadFromFile(string filePath)
	{
		base.LoadFromFile (filePath);
		//TEST
		for (int i = 1; i <= 10; i++) {
			_levelAttackList.Add (i * 10);
		}
	}

	public override int GetAttack (ClientData data)
	{
		int lv = data.Level;
		lv = System.Math.Min (System.Math.Max (lv, 1), 10);
		int attack = _levelAttackList [lv - 1];
		return attack + data.AttackPlus;
	}
}

 

接着,我们定义了怪兽的类,包含了怪兽的等级和攻击力加成。在创建怪兽对象的时候,我们就可以根据这两个值的不同,获取不同的攻击力。因为是可变的,所以被定义为外部数据:

 

public class ClientData
{
	public int Level {get;set;}
	public int AttackPlus { get; set;}
}

那么如何共享对象呢?这里需要建立一个享元工厂:

 

public static class LevelDataFactory
{
	public static Dictionary<int, LevelData> _monsterLevelData = new Dictionary<int, LevelData>();
	public static LevelData GetMonsterLevelData(int monID)
	{
		if (!_monsterLevelData.ContainsKey (monID)) {
			_monsterLevelData [monID] = new MonsterLevelData (monID);
		}
		return _monsterLevelData [monID];
	}
}

因为我们希望相同的怪兽应该拥有相同的等级数据,所以我们使用这样一个享元工厂来创建等级数据,对于相同的怪兽,它们共享同一个等级数据。

使用:

		LevelData levelData = LevelDataFactory.GetMonsterLevelData (1);
		ClientData monster = new ClientData ();
		monster.Level = 3;
		monster.AttackPlus = 5;
		Console.WriteLine (levelData.GetAttack(monster));

这里有一个问题,因为将等级、攻击加成等信息当做外部状态,放在了ClientData里,我们修改这些值得时候,是修改ClientData的实例,但是获取最终结果的时候,却调用的是levelData的方法GetAttack。这样不是很方便,并且容易造成困扰。所以我们可以考虑修改享元模式,把GetAttack的方法写在用户数据里。

为LevelData增加一个抽象方法:

	public abstract int GetLevelAttack(int lv);

MonsterLevelData里添加实现:

	public override int GetLevelAttack(int lv)
	{
		lv = System.Math.Min (System.Math.Max (lv, 1), 10);
		return _levelAttackList[lv - 1];
	}

创建继承自ClientData的Monster类:

public class Monster : ClientData
{
	LevelData _levelData;
	public Monster(int monID)
	{
		_levelData = LevelDataFactory.GetMonsterLevelData (monID);
	}
	public int GetAttack()
	{
		return _levelData.GetLevelAttack (Level) + AttackPlus;
	}
}

使用:

		Monster mon = new Monster (2);
		mon.Level = 3;
		mon.AttackPlus = 4;
		Console.WriteLine (mon.GetAttack ());
这样就将内部状态里的方法移动到了外部状态里。调用很方便,也节省了内存。


享元模式的优点在于:减少了运行时对象实例的个数,节省了内存,并且可以集中管理对象的多个状态。

缺点也很明显:个体之间无法相对独立,拥有不同的行为。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值