2015-04-27 2 views
6

Я столкнулся с проблемой в игре, которую я делаю на C#. Это простая игра на основе плитки, и проблема возникла из-за того, что я пытаюсь сделать следующее:Использование GetType/instanceof в C# по сравнению с альтернативами

Скажем, у нас есть основные типы плитки, круги и бриллианты, которые являются подклассами Плитки. Вместо того, чтобы круги только соответствовали кругам, я попытался извлечь поведение «совпадений» в абстрактный метод Tile: canMatchWith (Tile t). Плитки также имеют два способа добавить/удалить плитки, с которыми они могут сравниться.

Так скажите, что у нас есть круглая черепица в середине нашей игры, и у нас есть powerup, в котором говорится: «Круги на круге могут совпадать с квадратными черепицами в этом повороте». Я бы прошел через все плитки Circle и сказал circleTile.addCanMatchWith (typeof (Square)). Внутри у нас есть список canMatchWith.

Затем я хочу сказать: «Круги больше не могут совпадать с квадратами» и просто сказать circleTile.removeCanMatchWith (typeOf (Square)).

Это мое текущее решение, и оно отлично работает с недостатками производительности, которые я заметил (это игра на основе плитки, поэтому эти типы оцениваются только один раз за «ход», а не за кадром). Тем не менее, голос в моей голове говорит мне, что это плохой способ выполнить это поведение. Поэтому у меня есть несколько альтернатив:

  1. Enums ... Каждая плитка может состоять из переменной типа Tiletype. Это будет инициализировано в конструкторе и задано Type.SQUARE для квадратов и т. Д. Затем каждая Плитка будет иметь список canMatchWith, а функциональность такая же, как и моя первоначальная реализация. За исключением этого случая, это немного сложнее. Скажем, у меня есть некоторые подклассы окружности, овальные и elipse. Я хочу, чтобы овалы могли соответствовать ТОЛЬКО квадратам, но elipses могут соответствовать всем кругам, а не квадратам.

Проблема здесь является избыточность, мой перечисление будет теперь Овальные и ELIPSE, а также и класс Elipse бы (круг, овал, ELIPSE TileTypes) в качестве типов он может сравниться с. Это полностью избыточно, я хочу просто сказать «Круг», который я мог бы с помощью типов. Я полагаю, что Tiles может иметь TileType baseType и TileType actualType.

  1. Некоторая форма поведения. Забудьте подклассы Tile, просто укажите методы Tiles и переменную экземпляра для List. Затем во время выполнения мы можем просто сказать someTile.addCanMatch (новый CircleMatchBehavior()). Это кажется глупым, поскольку у меня будет куча классов, просто говорящих, что вы можете соответствовать определенной форме.

Таким образом, я пытаюсь добиться того, чтобы несколько типов объектов могли взаимодействовать с любым количеством разных типов. Вопрос в том, что я должен использовать для Типа. Можно ли использовать GetType здесь? Перечни? Или есть лучшая стратегия, которую кто-то порекомендовал бы? Я стараюсь быть как можно более общим, эти плитки не должны иметь каких-либо жестко закодированных зависимостей от других фрагментов и должны иметь возможность изменять, с кем они могут взаимодействовать «на лету». Скажем, я делаю новый подкласс подкласса, пентагон ... ну, Pentagons могут совпадать с квадратами, кругами и Pentagons. Легко с моей реализацией, но что-то говорит мне, что это грязная практика ООП.

Мне кажется, что я должен использовать типы/перечисления, потому что я не пытаюсь сказать thisTile.addCanMatch (Tile someOtherObject). Это слишком специфично, я хочу, чтобы thisTile мог сопоставляться со всеми фрагментами, которые являются экземплярами определенного класса.

+0

Я боюсь, что я не достаточно опытный дизайнер, чтобы предложить полный дизайн для вас, но если не фактические * * функциональные различия в способе кругов и Овалы работают, может быть лучше иметь все экземпляры класса 'Tile', а затем установить какой-то атрибут' Behavior'. Все круги на доске могут совместно использовать один и тот же объект 'Behavior', а затем, когда правила изменяются для поворота, вы можете установить изменения на этом объекте' Behavior'. Это может привести к меньшему дублированию кода, если я понимаю ситуацию. – Katana314

+0

Если я правильно понимаю вас, это похоже на мой № 2 Поведение. Мой страх здесь - это взрыв поведения. Например, MatchesWithCirclesAndSquaresBehavior. Посмотрите, что я получаю? Если есть 5 типов плиток, тогда у нас есть 5 выбрать 1 плюс 5 выбрать 2 плюс 5 выбрать 3 плюс 5 выбрать 4 плюс 5 выбрать 5 комбинаций поведения. Это кажется еще хуже, чем GetType. Однако я мог бы не понимать что-то в вашем предложении. Плитки сами делают все, что хотят, можно сделать анимацию взрыва и т. Д. Это только потому, что плитка может совпадать с другой. – user2045279

+0

Я не предлагал Поведение быть Enum или не поддающимся модификации классу. Вы можете подклассифицировать его способами, но в первую очередь я ожидаю, что он будет иметь внутреннюю коллекцию, представляющую тип (Circle) и типы (ы), которые он может сопоставить (квадрат). Затем вы можете изменить это с точки зрения данных. Я определенно согласен с тем, что все, что приведет к массивным блокам if/else/switch, следует избегать в пользу хорошей настройки объекта, но мы также хотим избежать написания нового класса для учета некоторых не очень новых изменений в поведении. – Katana314

ответ

2

Если все формы аналогичного типа всегда будут разделять поведение, тогда имеет смысл не сохранять это поведение на уровне «на один экземпляр».Вместо этого вы можете иметь «CanMatchManager», который хранит словарь списков, индексированных по типу формы. Затем, когда круг пытается сравнить соответствие, он запрашивает типы, которые он может совместить с MatchManager. Кроме того, MatchManager может принимать две фигуры и определять, соответствуют ли они. Это Mediator Pattern

+1

Интересно, что-то вроде: CanMatchManager :: canMatch (Tile one, Tile two) {return dictionary [one] .contains (two)}, где словарные карты Tile Введите список подходящих типов. Затем, если я хочу добавить взаимодействие, я могу сказать tileManager.addInteraction (Tile one, Tile two), и он обновляется, как {dict [one] .add (two), dict [two] .add (one)}. Это кажется более эффективным, чем то, что я делаю, с точки зрения требуемой памяти (возьмите его из экземпляров, отдайте его менеджеру). Но он по-прежнему использует GetType правильно? – user2045279

+0

Мой настоящий вопрос: «Можно ли использовать GetType» для такого рода проблем. Или было бы целесообразно реструктурировать мою программу, чтобы иметь возможность не использовать имена классов (типы). Часть меня говорит: «Если это работает, и код не сложный, возможно, это нормально», но также и «Instanceof, и это вонючий вонючий дизайн». Чтобы уточнить первый комментарий, словарь будет выглядеть в виде словаря <Тип, список >. Как мы получаем типы? someObject.GetType() и typeOf (SomeClass) – user2045279

+0

Он по-прежнему использует GetType, но может легко работать с Dictionary. По крайней мере, потому что это центральный, вы можете легко переключить его работу на Enum. Моя большая проблема с типами Enum - нет хорошего способа, чтобы класс сказал, что это Enum, статически, и в какой-то момент кажется, что он дублирует систему уже на месте. Кроме того, с типами вы всегда можете использовать отражение, чтобы ваш менеджер мог также работать с наследованием ... Я отредактировал свой ответ на ссылку на документацию по шаблону. – Psymunn

2

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

public class TypeMatchManager 
    { 
     private Dictionary<Type, List<Type>> savedMatches = new Dictionary<Type, List<Type>>(); 

     public TypeMatchManager() { } 

     public void AddMatch(Type firstType, Type secondType) 
     { 
      this.addToList(firstType, secondType); 
      this.addToList(secondType, firstType); 
     } 

     public void DeleteMatch(Type firstType, Type secondType) 
     { 
      this.deleteFromList(firstType, secondType); 
      this.deleteFromList(secondType, firstType); 
     } 

     public bool CanMatch(Type firstType, Type secondType) 
     { 
      List<Type> firstTypeList = this.findListForType(firstType); 
      List<Type> secondTypeList = this.findListForType(secondType); 
      return (firstTypeList.Contains(secondType) || secondTypeList.Contains(firstType)); 
     } 

     private void addToList(Type firstType, Type secondType) 
     { 
      var matchingTypes = this.findListForType(firstType); 
      if (!matchingTypes.Contains(secondType)) 
      { 
       matchingTypes.Add(secondType); 
      } 
     } 

     private void deleteFromList(Type firstType, Type secondType) 
     { 
      var matchingTypes = this.findListForType(firstType); 
      if (matchingTypes.Contains(secondType)) 
      { 
       matchingTypes.Remove(secondType); 
      } 
     } 

     private List<Type> findListForType(Type type) 
     { 
      foreach (var keyValuePair in savedMatches) 
      { 
       if (keyValuePair.Key == type) 
       { 
        return keyValuePair.Value; 
       } 
      } 
      savedMatches.Add(type, new List<Type>()); 
      return findListForType(type); 
     } 
    } 

Класс был спроектирован так, что не имеет значения, по каким параметрам вы указываете какой тип; он проверяет, что тип1.list имеет тип 2 и тип2.list имеет тип.

Простой пример:

 typeManager.AddMatch(a, b); 
     Console.WriteLine(typeManager.CanMatch(a, b)); // True 
     typeManager.DeleteMatch(b, a); 
     Console.WriteLine(typeManager.CanMatch(a, b)); // False 
     Console.WriteLine(typeManager.CanMatch(a, c)); // False 
     typeManager.AddMatch(a, c); 
     Console.WriteLine(typeManager.CanMatch(a, c)); // True 
     Console.WriteLine(typeManager.CanMatch(a, d)); // False 
     typeManager.AddMatch(b, d); 
     Console.WriteLine(typeManager.CanMatch(a, d)); // False 
     Console.WriteLine(typeManager.CanMatch(d, b)); // True 
     typeManager.DeleteMatch(d, b); 
     Console.WriteLine(typeManager.CanMatch(d, b)); // False 
     Console.WriteLine(typeManager.CanMatch(b, d)); // False 
+0

Именно так я и думал об этом. Очень простой и элегантный, и маленький. Спасибо за ваш вклад! – user2045279

Смежные вопросы