繁体   English   中英

C#中涉及集合,泛型和接口的设计问题

[英]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拥有不同的属性。 假设我们有PlayerEnemy将收到的以下数据:

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> ,其中TIData

interface IUpdateable<T> where T : IData
{
    void Update(T data);
}

因此,我们的实体是DrawableUpdateable

class Enemy : Drawable, IUpdateable<EnemyData>
{
    public void Update(EnemyData data) { }
}

class Player : Drawable, IUpdateable<PlayerData>
{
    public void Update(PlayerData data) {}
}

这就是游戏的基本结构。


现在,我需要存储这些DrawableUpdateable对象的Dictionary ,将Drawable的ID作为键存储,并存储一个包含DrawableUpdateable对象及其远程具体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.IDe1.ID )及其DataHolderplayerDataenemyData )和他们的价值。

类似于以下内容( 以下代码仅显示了我想要执行的操作,因此无法编译 ):

 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(); }
}

让您的PlayerEnemy从中派生,您的DataHolder会将其用作第一个通用约束。 这将允许您定义一个新的DataHolder<DrawableUpdatable<IData>, IData> 您将无法另外创建一个对象,因为EnemyPlayer都不会派生单个对象。 (您不能说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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM