Проблема проектирования с участием коллекций, обобщений и интерфейсов в C # - PullRequest
2 голосов
/ 27 июля 2010

Этот пост содержит много кода, но я был бы очень признателен, если бы вы взяли некоторые, чтобы прочитать и понять это ... и, надеюсь, придумали решение

Давайте предположим, что я структурирую сетевую игру, в которой сущности должны быть нарисованы, а некоторые из них соответствующим образом обновлены с сервера.

Класс 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) {}
}

Итак, это основная структура игры.


Теперь мне нужно хранить Dictionary этих Drawable и Updateable объектов, сохраняя идентификатор Drawable в качестве ключа и сложный объект, который содержит объекты 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, который содержит идентификатор сущности в качестве ключа (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);

Как мне создать такой словарь?

[Update]

Что касается использования, мне нужно будет сделать следующее:

 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

Но это приводит к ошибке времени компиляции:

Тип «Игрок» должен быть конвертируемым в 'IUpdateable ', чтобы использовать его в качестве параметра «Т» в универсальный класс 'DataHolder '

По какой причине это выброшено? Player реализует IUpdateable<PlayerData> и PlayerData реализует IData. Это проблема с дисперсией? И есть ли способ обойти это?

Ответы [ 4 ]

2 голосов
/ 27 июля 2010

Заставьте ваш DataHolder класс реализовать или наследовать неуниверсальный класс или интерфейс, а затем создать словарь этого (неуниверсального) типа.

1 голос
/ 27 июля 2010

Пожалуйста, внимательно прочитайте весь код.Я думаю, что это даст вам то, что вы хотите.Сначала вам нужен 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 для хранения всего и раскрыть свойство ключей

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

1 голос
/ 27 июля 2010

Я думаю, вам, возможно, понадобится создать новый класс для начала.

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>

Я думаю, что ваш дизайн слишком сложен для начала, так или иначе - он отпугнул бы любого нового разработчика, который пришел посмотреть на него и поддержать его.прежде чем исправить его с помощью еще более сложного кода.

1 голос
/ 27 июля 2010

То, как вы это написали, нет причины не объявлять ваш словарь таким образом:

Dictionary<int, object> list = new Dictionary<int, object>();

DataHolder не имеет открытых методов / свойств, кроме конструктора, поэтому это бесполезный типпосле того, как экземпляр был построен.Это не лучше, чем object

Если вы намереваетесь наложить на них публичные операции, а их аргументы и возвращаемые значения не требуют шаблонных значений, то вы можете извлечь не шаблонный интерфейс из DataHolderи сделайте это типом значения словаря.

...