Какой лучший способ разрешить комбинаторный взрыв взаимодействий?

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

Позвольте назвать его 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 может взаимодействовать друг с другом. Например, a GreenShip, который находится как в GravityWell, так и в NebulaField, может получить некоторый синергетический эффект между генерируемыми SpeedEffect и ShieldEffect. В таких случаях синергетический эффект сам по себе является Effect - например, из этого взаимодействия может возникнуть a 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 на них, добавив модификаторы к их существующим атрибутам и свойствам.

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

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

Ответ 1

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

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

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

Ответ 2

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

http://steve-yegge.blogspot.com/2008/10/universal-design-pattern.html

Ответ 3

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

В принципе, я бы создал базовый класс Ship, из которого вытекают все конкретные Суды. У этого класса были бы методы:

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

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

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

Ответ 5

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

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

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

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

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

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

IShip myShip = myShip.AddPhenomena(PhenomenaGeneratedByAFactoryForThisShip);

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

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

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

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

Ответ 6

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

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

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

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

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

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

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

Ответ 7

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

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

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

код 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 одним из них,

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