Как расширить состояния из нескольких классов - PullRequest
4 голосов
/ 06 мая 2011

(Обратите внимание: знание карточной игры Magic: The Gathering будет плюсом. Извините, я не знаю, как это проще.)

Я столкнулся с проблемой, используя Java , которую я опишу следующим образом ... У меня есть фундаментальный класс, называемый Card со всеми следующими атрибутами:

public class Card{
    String Name;
    String RulesText;
    String FlavorText;
    String Cost;
    int ConvertedCost;
    String Rarity;
    int Number;
}

Перманентный класс расширяет Карту и, в свою очередь, расширяется классами Существо, Странник, Артефакт, Земля и Чары. Пока среди них только первые два имеют собственные поля:

public class Creature extends Permanent{
    int Power;
    int Toughness;
}
public class Planeswalker extends Permanent{
    int Loyalty;
}

Проблемы:

  • Некоторые постоянные объекты могут подвергаться методам, влияющим на объект его подклассов, например, как артефакты и существа, или как артефакты и земли. Я знаю, что могу использовать интерфейсы для расширения поведения, но я не могу сделать это для расширения состояния, если не использую абстрактные классы, и в некоторых случаях мне нужен объект с состоянием Артефакты и Существа.
  • На большинство постоянных объектов данного подкласса нельзя повлиять методом, нацеленным на объект другого подкласса . (То есть метод, нацеленный только на объекты чар, не может влиять на объекты существ.)

Ответы [ 5 ]

4 голосов
/ 06 мая 2011

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

В MTG есть случаи, когда Перманенты не связаны напрямую с картой (такие карты, как The Hive генерируют существ, представленных только токенами, у них нет представления карты), поэтому иерархия наследования для этого не работает. Хотя вам нужно будет сохранить связь между картой и вызванным ею существом (чтобы вы знали, когда отправить карту обратно в колоду сброса), я не думаю, что конкретные отношения наследования, которые вы выложили, будут полезно. Вам может понадобиться одна иерархия для карт, а другая - для постоянных. Не игнорируйте возможность иерархий (потому что есть много отношений наследования, которые можно использовать с пользой), просто будьте осторожны, чтобы разделить отдельные вещи.

Я бы порекомендовал прочесть пост блога Стива Йегге на Шаблон универсального дизайна , где он говорит об использовании шаблона свойств для своей игры (которая кажется такой же гибкой, как MTG). Очевидно, что многие характеристики вещей в игре изменчивы (включая то, что является существом, а что нет, поскольку есть карты, которые меняют земли на существа), и система управления свойствами и изменениями в них (включая истекающие временные изменения) будет важно. Сама по себе иерархия классов не будет достаточно гибкой, чтобы учесть множество эффектов в MTG.

Что делает MTG интересным, так это то, что у вас есть эта якобы предметная область (состоящая из таких вещей, как существа, заклинания, артефакты, земли и т. Д.), Которая кажется понятной и надежной, но правила позволяют этим вещам изменяться непредсказуемым образом (тем более что всегда можно вводить новые карты). Предметный домен реализуется через игровой домен (набор правил, которые управляют предметным доменом). Если вы жестко закодируете предметную область в своей реализации (выполняя такие вещи, как представление Lands или Artifacts путем расширения суперкласса или реализации интерфейса), всегда будут вещи, с которыми вы столкнетесь, с которыми вы не сможете справиться, и решение этих проблем может быть трудным или невозможным. Прочитайте правила, помечая введенную техническую терминологию (карты, перманенты, токены, эффекты и т. Д.), Потому что это реальная область, которую вам нужно реализовать.

2 голосов
/ 06 мая 2011

Рассмотрите возможность использования StateMachine для этого.

У вас будет CreatureStateMachine, а также ArtifactStateMachine. Объект, реализующий оба интерфейса, может быть передан любому StateMachine. Каждый StateMachine будет отвечать за управление значениями другого набора атрибутов.

Сам объект генерирует события и передает их в StateMachines, которые либо игнорируют, либо обрабатывают их соответственно. StateMachine обновляет состояние управляемого объекта, а сам объект знает только, в каком состоянии он находится.

1 голос
/ 06 мая 2011

Я вообще не знаю эту игру, но просто следую тому, что вы здесь говорите:

Возможно, многоуровневая иерархия решит ваши проблемы. Например, если и у Артефактов, и у Земель есть общее поведение, вместо того, чтобы говорить, что Артефакт расширяет Перманент, а Земля расширяет Перманент, вы можете создать другой класс, назовем его Свойством, а затем сказать, что Свойство расширяет Перманент, Артефакт расширяет Свойство, Земля расширяет Свойство. (Я имею в виду «собственность» в смысле «реальная собственность» и «личная собственность», а не собственность объекта. В любом случае.) Тогда любые свойства или данные, общие как для Артефакта, так и для Земли, могут находиться в Собственности.

Но если есть поведение, общее для Артефакта и Земли, которое не применимо к Существу, и другое поведение, общее для Артефакта и Существа, которое не применимо к Земле, этот подход не будет работать.

Класс Java не может расширять более одного класса, но, конечно, он может реализовывать любое количество классов. Таким образом, вы можете создать интерфейсы для общего поведения для любого заданного набора объектов, например, интерфейс Movable, который будет реализован с помощью Artifact и Creature, и интерфейс Property, который будет реализован с помощью Artifact и Land и т. Д. Но, как я уверен, что вы понимаете, это наследует только объявления, а не код. Таким образом, в конечном итоге вам придется реализовывать один и тот же код несколько раз.

Одна вещь, которую я иногда делал, - это создание другого класса для реализации поведения, а затем «настоящий» класс просто перенаправляет все в служебный класс. Так что если - и снова я не знаю игру, поэтому я просто придумываю примеры - и Артефакту, и Земле нужна функция «купить», вы можете создать интерфейс Property, который расширяют и Артефакт, и Землю, создать Класс PropertyImplementation, который включает в себя фактический код, и тогда у вас будет что-то вроде:

public class PropertyImplementation
{
  public static void buy(Property property, Buyer buyer, int gold)
  {
    buyer.purse-=gold;
    property.owner=buyer;
    ... etc, whatever ...
  }
}
public interface Property
{
  public static void buy(Property property, Buyer buyer, int gold);
}
public class Land extends Permanent implements Property
{
  public void buy(Buyer buyer, int gold)
  {
    PropertyImplementation.buy(this, buyer, gold);
  }
  ... other stuff ...
}
public class Artifact extends Permanent implements Property
{
  public void buy(Buyer buyer, int gold)
  {
    PropertyImplementation.buy(this, buyer, gold);
  }
  ... other stuff ...
}

Менее сложный, но иногда довольно практичный подход - просто генерировать ошибки при совершении неприменимых вызовов. Как:

public class Permanent
{
  public void buy(Buyer buyer, int gold)
  throws InapplicableException
  {
    buyer.purse-=gold;
    this.owner=buyer;
    ... etc ...
  }
}
public class Plainsman extends Permanent
{
  // override
  public void buy(Buyer buy, int gold)
  throws InapplicableException
  {
    throw new InapplicableException("You can't buy a Plainsman! That would be slavery!");
  }
}
1 голос
/ 06 мая 2011

У вас будут проблемы хуже, когда вы начнете иметь дело с Артефактными Существами, или Артефактными Землями и т. Д.

Вам необходимо переосмыслить отношения IS-A в вашей модели.Множественное наследование не будет возможным.Но вы можете использовать определенные шаблоны проектирования, например, шаблоны Visitor или Delegate.Эффекты могут использовать шаблон Command.Может быть даже полезно, чтобы член базового класса был «TypeTags» - список всех действительных правил таргетинга.

Например, вы можете создать существо (псевдокод):

Creature thing = new Creature("My Name", toughness, power, {artifact,creature,enchantment});

Каждая операция «Эффект» будет принимать объект карты:

Card doEffect(InCard, validTargets) {
  if (validTargets.Intersection(Incard.targets).length > 0) //we have valid targets
    return actuallyDoEffect(InCard);
  else
    return InCard;
}
0 голосов
/ 06 мая 2011

Редактировать : Хотя это похоже на проблему наследования, которая выиграет от простой реструктуризации, я отдам это тем, у кого есть опыт Магии.

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