Какой лучший способ разрешить комбинаторный взрыв взаимодействий? - PullRequest
10 голосов
/ 26 марта 2009

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

Давайте назовем это DeathBlaster 4: Deathening . В DB4 у вас есть ряд Ship объектов, которые периодически и случайным образом встречают Phenomena во время своего путешествия. У данного Phenomenon может быть ноль, один или более Effects на Ship, с которым он сталкивается. Например, у нас может быть четыре вида Ships и три вида Phenomena.

                              Phenomena
              ==========================================
Ships         GravityWell     BlackHole      NebulaField
------------  ------------------------------------------
RedShip       +20% speed      -50% power     -50% shield
BlueShip      no effect       invulnerable   death              Effects of Various
GreenShip     -20% speed      death          +50% shield        Phenomena on Ships
YellowShip    death           +50% power     no effect    

Кроме того, Effects может взаимодействовать друг с другом. Например, GreenShip, который присутствует как в GravityWell, так и в NebulaField, может получить некоторую синергию между сгенерированными SpeedEffect и ShieldEffect. В таких случаях синергетический эффект сам по себе равен Effect - например, в результате этого взаимодействия может быть PowerLevelSynergyEffect. Никакой информации, кроме набора Effects, действующего на Ship, не требуется для определения окончательного результата.

Вы можете начать видеть проблему, возникающую здесь. В качестве наивного первого подхода либо каждый Ship должен будет знать, как обращаться с каждым Phenomenon, либо каждый Phenomenon должен знать о каждом Ship. Это явно неприемлемо, поэтому мы хотели бы перенести эти обязанности в другое место. Очевидно, здесь есть по крайней мере один внешний класс, возможно, какой-нибудь Mediator или Visitor.

Но какой лучший способ сделать это? Идеальное решение, вероятно, будет иметь следующие свойства:

  • Добавить новый Ship так же просто, как добавить новый Phenomenon.
  • Взаимодействия, которые не дают эффекта, являются стандартными и не требуют дополнительного кода для представления. Соглашение по конфигурации.
  • Понимает, как Effects взаимодействует друг с другом, и способен управлять этими взаимодействиями, чтобы решить, каким будет конечный результат.

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

<Ч /> <Ч />

Последующее обновление: Спасибо всем за ответы. Вот что я в итоге делаю. Моим главным наблюдением было то, что число различных Effects кажется небольшим по сравнению с числом возможных Phenomena & times; Ships взаимодействий. То есть, хотя существует множество возможных комбинаций взаимодействий, число видов результатов этих взаимодействий является меньшим числом.

Вы можете видеть, что, например, хотя в таблице 12 комбинаций взаимодействий, есть только пять видов эффектов: изменения скорости, изменения силы, модификации щита, неуязвимость, смерть .

Я ввел третий класс, InteractionResolver, для определения результата взаимодействий. Он содержит словарь, который сопоставляет пары Ship-Phenomenon с Effects (которые в основном являются делегатом, выполняющим эффект, и некоторыми метаданными). Каждому Ship вручается EffectStack, соответствующий Effects, который он испытывает, когда результат вычисления взаимодействия завершен.

Ships затем используйте EffectStack, чтобы определить фактический результат Effects для них, добавив модификаторы к их существующим атрибутам и свойствам.

Мне нравится это, потому что:

  1. Кораблям никогда не нужно знать о явлениях.
    • Сложность отношений Корабль-Явления абстрагируется в InteractionResolver.
    • Детали того, как разрешать множественные и, возможно, сложные эффекты, абстрагируются InteractionResolver. Корабли должны применять эффекты только по мере необходимости.
    • Это включает дополнительные полезные рефакторинги. Например, способ , в котором корабль обрабатывает эффекты, может быть дифференцирован путем создания EffectProcessorStrategy. По умолчанию может обрабатываться все эффекты, но, скажем, BossShip может игнорировать второстепенные эффекты при наличии другого EffectProcessorStrategy.

Ответы [ 7 ]

1 голос
/ 26 марта 2009

Интересным потенциальным вариантом будет использование варианта Шаблон посетителя .

Джудит Бишоп и Р. Найджел Хорспул написали статью о эффективности шаблона проектирования , в которой они объяснили различные варианты классического шаблона посетителя с использованием функций C # 3.

В частности, я хотел бы взглянуть на то, как они работают с делегатами для обработки шаблона посетителя. Использование списка или стека делегатов могло бы потенциально дать вам интересный способ обработки нескольких эффектов от нескольких объектов, и было бы намного проще расширить любую сторону иерархии классов (добавить корабли или добавить эффекты) без огромных критических изменений кода.

0 голосов
/ 27 марта 2009

Я думаю, что ответ на вопрос зависит от того, насколько хорошо задан вопрос.

Я думаю, что путь к дизайну зависит от того, что вопрос (или вопрос пойдет в будущем)

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

код Python здесь: (не тестировался и просто показывает для примера)

class Ship():
     def __init__(self,type):
         self.type=type
     def encounterPhenomena(self,type): # let Phenomena to process ship
         p = Phenomena(type)
         p.process(self)

class Phenomena():
     processTable = {}
     def __init__(self,type):
         self.type=type
     def process(self,ship):
         try:
             self.processTable[self.type](ship.type) #query the internal table to process ship
         except:
             pass #if Phenomena don't know this ship type then no process..
     def addType(type,processMethod):
         processTable[type]=processMethod #add new Phenomena, and add processMethod

def run():
    A = Ship(type = 'RedShip')
    A.encounterPhenomena(type='GravityWell');

Если метод процесса изменился, просто измените метод процесса в классе Phenomena.

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

или вы думаете, что есть другие вещи, не только явления должны изменить статус корабля (как и другие корабли, мелководье), вам нужно сохранить таблицу процессов в классе корабля и сделать Phenomena одним из них,

говорите еще раз, как дизайн зависит от вопроса о себе.

0 голосов
/ 26 марта 2009

либо каждый корабль должен знать как справиться с каждым явлением, или каждый феномен должен знать о каждом корабле.

Я думаю, что это ключ к вашей проблеме, и это правда, если каждое взаимодействие между кораблем и явлениями уникально. В созданной вами таблице это выглядит так, поэтому для n кораблей и m явлений необходимо указать n * m взаимодействие. Вы правильно чувствуете проблему с обслуживанием.

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

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

Таким образом, корабли, построенные со странным глиноземом, переживут черные дыры, а корабли, построенные без, могут двигаться быстрее. 1 свойство позволяет указывать 2 вещи (1 бит = 2 возможности). Корабли, построенные с использованием двигателей Corbite, будут двигаться быстрее в зоне деформации. Корабли с двигателями с корбитами и странным глиноземом получат 50% щита в поле туманности и т. Д.

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

Если есть M кораблей, то вам нужны только свойства log2 (M), чтобы дать каждому кораблю уникальное поведение.

0 голосов
/ 26 марта 2009

Интересный вопрос

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

Это может быть сохранено в xml-файле, проанализированном во время выполнения.

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

Предположим, что ваши корабли реализуют интерфейс IShip, и везде в вашем коде вы используете IShips.

Теперь предположим, что все ваши явления также реализуют интерфейс IShip (требуется для шаблона проектирования декоратора).

IShip myShip = myShip.AddPhenomena(PhenomenaGeneratedByAFactoryForThisShip);

В явлениях вы оборачиваете методы из исходного корабля, чтобы вы могли выполнить модификацию свойств и всего.

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

Удаление явления можно использовать, пройдясь по стопке украшенных кораблей и перемотав ее, поэтому я не вижу в этом никакой проблемы.

Что касается синергии, я думаю, что я бы использовал слегка модифицированный феномен, который работает, только если у целевого корабля есть все явления на себе.

0 голосов
/ 26 марта 2009

Звучит как классическая проблема множественной отправки для меня.

0 голосов
/ 26 марта 2009

Это похоже на классическую дилемму полиморфизма ООП / посетителя. Тем не менее, ваши требования делают это проще.

По сути, я бы создал базовый класс Ship, из которого происходят все конкретные корабли. Этот класс будет иметь методы:

class Ship
{
  void encounterBlackHole() {}
  void encounterNebula() {}
  ... etc. ...
};

с пустыми телами по умолчанию. Когда вы добавляете новый феномен, вы просто добавляете новый метод с пустым телом. (Методы могут иметь аргументы, такие как координаты или вес черной дыры и т. Д.)

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

0 голосов
/ 26 марта 2009

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

http://steve -yegge.blogspot.com / 2008/10 / универсальный дизайн-pattern.html

...