Фон
Вот настоящая проблема, над которой я работаю: я хочу представить карты в карточной игре Magic: The Gathering . Большинство карт в игре выглядят нормально, но некоторые из них разделены на две части, каждая со своим именем. Каждая половина этих двухкомпонентных карт рассматривается как сама карта. Поэтому для ясности я буду использовать Card
только для обозначения чего-то, что является либо обычной картой, либо половиной карты, состоящей из двух частей (другими словами, чем-то, имеющим только одно имя).
Итак, у нас есть базовый тип, карта. Цель этих объектов на самом деле просто держать свойства карты. Они на самом деле ничего не делают сами.
interface Card {
String name();
String text();
// etc
}
Есть два подкласса Card
, которые я называю PartialCard
(половина карты из двух частей) и WholeCard
(обычная карта). PartialCard
имеет два дополнительных метода: PartialCard otherPart()
и boolean isFirstPart()
.
Представители
Если у меня есть колода, она должна состоять из WholeCard
s, а не Card
s, поскольку a Card
может быть a PartialCard
, и это не имеет смысла. Поэтому я хочу, чтобы объект представлял «физическую карту», то есть что-то, что может представлять один WholeCard
или два PartialCard
элемента s. Я предварительно вызываю этот тип Representative
, и у Card
меня будет метод getRepresentative()
. A Representative
почти не предоставит прямой информации о картах, которые он представляет, он только укажет на нее / них. Теперь моя блестящая / сумасшедшая / глупая идея (вы решаете), что WholeCard наследует от обоих Card
и Representative
. В конце концов, это карты, которые представляют себя! WholeCards может реализовать getRepresentative
как return this;
.
Что касается PartialCards
, они не представляют себя, но у них есть внешнее Representative
, которое не является Card
, но предоставляет методы для доступа к двум PartialCard
s.
Я думаю, что эта иерархия типов имеет смысл, но она сложная. Если мы рассматриваем Card
s как «концептуальные карты», а Representative
s как «физические карты», то большинство карт оба! Я думаю, вы могли бы поспорить, что физические карты на самом деле содержат концептуальные карты, и что это не одно и то же , но я бы сказал, что это так.
Необходимость литья типов
Поскольку PartialCard
s и WholeCards
есть оба Card
s, и обычно нет веских причин для их разделения, я бы обычно просто работал с ними Collection<Card>
. Поэтому иногда мне нужно приводить PartialCard
s, чтобы получить доступ к их дополнительным методам. Прямо сейчас я использую систему, описанную здесь, потому что я действительно не люблю явные приведения. И, как Card
, Representative
будет необходимо привести либо к WholeCard
или Composite
, чтобы получить доступ к фактическим Card
s, которые они представляют.
Так что просто для резюме:
- Базовый тип
Representative
- Базовый тип
Card
- Тип
WholeCard extends Card, Representative
(доступ не требуется, он представляет себя) - Тип
PartialCard extends Card
(дает доступ к другой части) - Тип
Composite extends Representative
(дает доступ к обеим частям)
Это безумие? Я думаю, что это действительно имеет большой смысл, но я, честно говоря, не уверен.
источник
Ответы:
Мне кажется, у вас должен быть такой класс
Код, связанный с физической картой, может иметь дело с классом физической карты, и код, связанный с логической картой, может справиться с этим.
Неважно, думаете ли вы, что физическая и логическая карты - это одно и то же. Не думайте, что только потому, что они являются одним и тем же физическим объектом, они должны быть одним и тем же объектом в вашем коде. Важно то, облегчит ли принятие этой модели кодировщик для чтения и записи. Дело в том, что принятие более простой модели, в которой каждая физическая карта обрабатывается как набор логических карт последовательно, 100% времени, приведет к более простому коду.
источник
Чтобы быть грубым, я думаю, что предлагаемое решение является слишком ограничительным и слишком искаженным и не связанным с физической реальностью - это модели с небольшим преимуществом.
Я бы предложил одну из двух альтернатив:
Вариант 1. Рассматривайте его как отдельную карту, обозначенную как Half A // Half B , как списки сайтов MTG Wear // Tear . Но разрешите вашей
Card
сущности содержать N каждого атрибута: имя для игры, стоимость маны, тип, редкость, текст, эффекты и т. Д.Вариант 2. Не все, что отличается от варианта 1, смоделируйте его в соответствии с физической реальностью. У вас есть
Card
объект, который представляет физическую карту . И его цель состоит в том, чтобы держать NPlayable
вещей. УPlayable
каждого из них может быть свое имя, мана-стоимость, список эффектов, список способностей и т. Д. А у вашего «физического»Card
может быть свой собственный идентификатор (или имя), который является составной частью имени каждогоPlayable
, во многом как база данных MTG, кажется, делает.Я думаю, что любой из этих вариантов довольно близок к физической реальности. И я думаю, что это будет полезно для всех, кто смотрит на ваш код. (Как и ты сам через 6 месяцев.)
источник
Это предложение является признаком того, что в вашем дизайне что-то не так: в ООП каждый класс должен иметь ровно одну роль, а отсутствие поведения выявляет потенциальный класс данных , который является неприятным запахом в коде.
ИМХО, это звучит немного странно, и даже немного странно. Объект типа «Карта» должен представлять собой карту. Период.
Я ничего не знаю о Магии: сбор , но я думаю, что вы хотите использовать свои карты аналогичным образом, независимо от их фактической структуры: вы хотите отобразить строковое представление, вы хотите вычислить значение атаки и т. Д.
Для описываемой проблемы я бы порекомендовал Composit Design Pattern , несмотря на то, что этот DP обычно представлен для решения более общей проблемы:
Card
интерфейс, как вы уже сделали.ConcreteCard
, который реализуетCard
и определяет простую лицевую карту. Не стесняйтесь ставить поведение обычной карты в этом классе.CompositeCard
, который реализуетCard
и имеет два дополнительных (и априорных частных)Card
. Позвоним имleftCard
иrightCard
.Элегантность этого подхода состоит в том, что a
CompositeCard
содержит две карты, которые могут быть либо ConcreteCard, либо CompositeCard. В вашей игре,leftCard
иrightCard
, вероятно, будут систематическиConcreteCard
, но Design Pattern позволяет вам создавать композиции более высокого уровня бесплатно, если хотите. Ваши манипуляции с картами не будут учитывать реальный тип ваших карт, и поэтому вам не нужны такие вещи, как приведение к подклассу.CompositeCard
Card
Конечно, необходимо реализовать методы, указанные в , и делать это, принимая во внимание тот факт, что такая карта состоит из 2 карт (плюс, если хотите, что-то специфическое для самойCompositeCard
карты. Например, вы можете захотеть следующая реализация:Делая это, вы можете использовать
CompositeCard
точно так же, как вы делаете для любогоCard
, и конкретное поведение скрыто благодаря полиморфизму.Если вы уверены, что a
CompositeCard
всегда будет содержать два нормальныхCard
s, вы можете сохранить идею и просто использоватьConcreateCard
как тип дляleftCard
иrightCard
.источник
Card
вCompositeCard
вы реализуете шаблон декоратора . Я также рекомендую ОП использовать это решение, декоратор это путь!CompositeCard
не предоставляет дополнительные методы,CompositeCard
является только декоратором.Может быть, все это Карта, когда она находится в колоде или на кладбище, и когда вы разыгрываете ее, вы создаете Существо, Землю, Чары и т. Д. Из одного или нескольких объектов Карты, каждый из которых реализует или расширяет Воспроизводимый. Затем составной становится одиночным играбельным, конструктор которого берет две частичные карты, а карта с кикером становится играбельным, конструктор которого принимает аргумент маны. Тип отражает то, что вы можете сделать с ним (нарисовать, заблокировать, рассеять, нажать) и что может повлиять на это. Или играбельная - это просто карта, которую нужно аккуратно вернуть (потерять все бонусы и фишки, разбить ее), когда вывести из игры, если действительно полезно использовать тот же интерфейс для вызова карты и прогнозирования ее действия.
Возможно, карты и играбельные имеют эффект.
источник
Шаблон посетителя классический метод для извлечения скрытой информации типа. Мы можем использовать это (небольшое изменение здесь) здесь, чтобы различать два типа, даже когда они хранятся в переменных более высокой абстракции.
Давайте начнем с этой более высокой абстракции,
Card
интерфейса:Там может быть немного больше поведения на
Card
интерфейсе , но большинство методов получения свойств переходят в новый классCardProperties
:Теперь мы можем иметь
SimpleCard
представить целую карту с одним набором свойств:Мы видим, как
CardProperties
и что еще предстоит написатьCardVisitor
. Давайте сделаем так,CompoundCard
чтобы представить карту с двумя лицами:CardVisitor
Начинает появляться. Давайте попробуем написать этот интерфейс сейчас:(Это первая версия интерфейса на данный момент. Мы можем внести улучшения, которые будут обсуждаться позже.)
Теперь мы уточнили все части. Теперь нам просто нужно собрать их вместе:
Среда выполнения будет обрабатывать отправку к правильной версии
#visit
метода посредством полиморфизма, а не пытаться его сломать.Вместо того, чтобы использовать анонимный класс, вы можете даже продвигать
CardVisitor
преобразовать его во внутренний класс или даже в полный класс, если поведение можно использовать повторно или если вы хотите поменять поведение во время выполнения.Мы можем использовать классы такими, какие они есть сейчас, но мы можем внести некоторые улучшения в
CardVisitor
интерфейс. Например, может наступить момент, когда уCard
s может быть три, четыре или пять лиц. Вместо того, чтобы добавлять новые методы для реализации, мы могли бы просто использовать второй метод take и array вместо двух параметров. Это имеет смысл, если к многогранным картам относятся по-разному, но количество лиц выше одной считается одинаковым.Мы могли бы также преобразовать
CardVisitor
в абстрактный класс вместо интерфейса, и иметь пустые реализации для всех методов. Это позволяет нам реализовывать только интересующее нас поведение (возможно, нас интересуют только односторонниеCard
). Мы также можем добавлять новые методы, не заставляя каждый существующий класс реализовывать эти методы или не компилировать их.источник