[英]A design problem involving Collections, Generics and Interfaces in C#
这篇文章包含很多代码,但是如果您花一些时间阅读和理解它,我将不胜感激...并希望提出解决方案
假设我正在构建一个网络游戏,需要绘制实体,并从服务器中相应地更新其中的一些实体。
Drawable
类负责在屏幕上绘制实体:
class Drawable
{
public int ID { get; set; } // I put the ID here instead of having another
// class that Drawable derives from, as to not
// further complicate the example code.
public void Draw() { }
}
从服务器接收的数据实现了IData
,每个具体的IData
拥有不同的属性。 假设我们有Player
和Enemy
将收到的以下数据:
interface IData
{
int ID { get; set; }
}
class PlayerData : IData
{
public int ID { get; set; }
public string Name { get; set; }
}
class EnemyData : IData
{
public int ID { get; set; }
public int Damage { get; set; }
}
应该可以从服务器更新的游戏实体实现IUpdateable<T>
,其中T
是IData
:
interface IUpdateable<T> where T : IData
{
void Update(T data);
}
因此,我们的实体是Drawable
和Updateable
:
class Enemy : Drawable, IUpdateable<EnemyData>
{
public void Update(EnemyData data) { }
}
class Player : Drawable, IUpdateable<PlayerData>
{
public void Update(PlayerData data) {}
}
这就是游戏的基本结构。
现在,我需要存储这些Drawable
和Updateable
对象的Dictionary
,将Drawable
的ID作为键存储,并存储一个包含Drawable
和Updateable
对象及其远程具体IData
的复杂对象:
class DataHolder<T, T1> where T:Drawable, IUpdateable<T1> where T1:IData
{
public T Entity{ get; set;}
public IData Data{ get; set;}
public DataHolder(T entity, IData data)
{
Entity = entity;
Data = data;
}
}
举例来说,假设我目前有以下实体:
var p1 = new Player();
var p1Data = new PlayerData();
var e1 = new Enemy();
var e1Data = new EnemyData();
var playerData = new DataHolder<Player, PlayerData>(p1, p1Data);
var enemyData = new DataHolder<Enemy, EnemyData>(e1, e1Data);
我现在需要有一个Dictionary
持有的实体的ID作为关键字( p1.ID
和e1.ID
)及其DataHolder
( playerData
和enemyData
)和他们的价值。
类似于以下内容( 以下代码仅显示了我想要执行的操作,因此无法编译 ):
Dictionary<int, DataHolder> list = new Dictionary<int, DataHolder>();
list.Add(p1.ID, playerData);
list.Add(e1.ID, enemyData);
如何构建这样的词典?
[更新]
关于用法,我将需要执行以下操作:
foreach (var value in list.Values)
{
var entity = value.Entity;
entity.Update(value.Data);
}
我还尝试将DataHolder
的设计更改为以下内容:
class DataHolder<T> where T:Drawable, IUpdateable<IData>
{
public T Entity{ get; set;}
public IData Data{ get; set;}
public DataHolder(T entity, IData data)
{
Entity = entity;
Data = data;
}
}
然后,我尝试了以下操作:
var playerData = new DataHolder<Player>(p1, p1Data); //error
但这会引发编译时错误:
类型“ Player”必须可转换为“ IUpdateable <IData>”,才能在通用类“ DataHolder <T>”中用作参数“ T”
出于什么原因抛出? Player
实现IUpdateable<PlayerData>
, PlayerData
实现IData
。 这是差异问题吗? 周围有什么办法吗?
使您的DataHolder
类实现或继承一个非泛型类或接口,然后制作该(非泛型)类型的字典。
您编写此代码的方式没有理由不声明字典:
Dictionary<int, object> list = new Dictionary<int, object>();
DataHolder
除了构造函数外没有其他公共方法/属性,因此在构造实例之后,它是无用的类型。 没有比object
更好
如果确实打算对它们进行公共操作,并且它们的参数和返回值不需要模板值,则可以从DataHolder
提取非模板接口,并使其成为字典的值类型。
我认为您可能首先需要创建一个新类。
class DrawableUpdatable<T> : Drawable, IUpdatable<T> {
public void Update() { base.Update(); }
}
让您的Player
和Enemy
从中派生,您的DataHolder会将其用作第一个通用约束。 这将允许您定义一个新的DataHolder<DrawableUpdatable<IData>, IData>
。 您将无法另外创建一个对象,因为Enemy
和Player
都不会派生单个对象。 (您不能说DataHolder<Drawable **and** IUpdatable<IData>, IData>
我认为您的设计从一开始就太过复杂了-它会让所有研究和维护它的新开发人员感到恐惧。 在考虑使用更复杂的代码修复它之前,我会考虑对其进行修改。
请仔细阅读所有代码。 我认为这将为您提供所需的东西。 首先,您需要一个IDrawable
接口
interface IDrawable { int ID { get; set; } }
再加上明显的class Drawable : IDrawable { .. }
则需要一个IEntity
接口
interface IEntity : IDrawable, IUpdatetable<IData> { }
连同实现
class Enemy : Drawable, IEntity
{
public Enemy(int id) : base(id) { }
public void Update(EnemyData data)
{ ... }
void IUpdatetable<IData>.Update(IData data)
{
Update(data as EnemyData);
}
}
class Player : Drawable, IEntity
{
public Player(int id) : base(id) { }
public void Update(PlayerData data)
{ ... }
void IUpdatetable<IData>.Update(IData data)
{
Update(data as PlayerData);
}
}
然后,您需要通用化DataHolder
interface IDataHolder
{
IEntity Entity { get; set; }
IData Data { get; set; }
}
class DataHolder<T,T1> : IDataHolder
where T : class, IEntity
where T1 : class, IData
{
T entity;
T1 data;
public DataHolder(T entity, T1 data)
{
this.entity = entity;
this.data = data;
}
public T Entity { get { return entity; } set { entity = value; } }
public T1 Data { get { return data; } set { data = value; } }
IEntity IDataHolder.Entity
{
get { return entity; }
set { entity = value as T; }
}
IData IDataHolder.Data
{
get { return data; }
set { data = value as T1; }
}
}
最后使用KeyedCollection来保存所有内容并公开keys属性
public class DataBag : KeyedCollection<int, IDataHolder>
{
protected override int GetKeyForItem(IDataHolder item)
{
return item.Entity.ID;
}
public ICollection<int> Keys
{
get { return Dictionary.Keys; }
}
}
这是测试代码:
public void TestCode()
{
Player p1 = new Player(100);
Enemy e1 = new Enemy(1);
PlayerData p1data = new PlayerData(11, "joe");
EnemyData e1data = new EnemyData(12, 1000);
DataHolder<Player, PlayerData> bag1
= new DataHolder<Player, PlayerData>(p1, p1data);
DataHolder<Enemy, EnemyData> bag2
= new DataHolder<Enemy, EnemyData>(e1, e1data);
Dictionary<int, IDataHolder> list = new Dictionary<int, IDataHolder>();
list.Add(p1.ID, bag1);
list.Add(e1.ID, bag2);
foreach (int id in list.Keys )
{
IDataHolder item = list[id];
// you can do this here:
// item.Entity.Update(item.Data);
// or get the type specific version below:
if (item.Entity is Player)
{
Player player = item.Entity as Player;
PlayerData pdata = item.Data as PlayerData;
player.Update(pdata);
Console.WriteLine("ID={0} PlayerName={1} DataId={2}",
player.ID, pdata.Name, pdata.ID);
}
else if (item.Entity is Enemy)
{
Enemy enemy = item.Entity as Enemy;
EnemyData edata = item.Data as EnemyData;
enemy.Update(edata);
Console.WriteLine("ID={0} EnemyDamage={1} DataId={2}",
enemy.ID, edata.Damage, edata.ID);
}
}
}
它带着微笑编译并产生输出
ID=100 PlayerName=joe DataId=11
ID=1 EnemyDamage=1000 DataId=12
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.