Использование наследования и полиморфизма для решения общей игровой проблемы - PullRequest
22 голосов
/ 05 мая 2010

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

public class Ogre
{
  int weight;
  int height;
  int axeLength;
}

public class Wizard
{
  int age;
  int IQ;
  int height;
}

В каждом классе я могу создать метод, называемый, скажем, battle (), который будет определять, кто победит, если Огр встретится и Огр или Волшебник встретит Волшебника. Вот пример. Если огр встречает огра, побеждает более тяжелый. Но если вес такой же, побеждает тот, у кого топор длиннее.

public Ogre battle(Ogre o)
{
  if (this.height > o.height) return this;
  else if (this.height < o.height) return o;
  else if (this.axeLength > o.axeLength) return this;
  else if (this.axeLength < o.axeLength) return o;
  else return this;    // default case
}

Мы можем сделать аналогичный метод для Wizards.

Но что, если Волшебник встречает Огра? Конечно, мы могли бы сделать метод для этого, сравнив, скажем, только высоту.

public Wizard battle(Ogre o)
{
  if (this.height > o.height) return this;
  else if (this.height < o.height) return o;
  else return this;
}

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

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

public class Character
{
  int height;

  public Character battle(Character c)
  {
    if (this.height > c.height) return this;
    else if (this.height < c.height) return c;
    else return this;
  }
}

Есть ли лучший способ организовать занятия? Я посмотрел на шаблон стратегии и шаблон посредника, но я не уверен, как кто-то из них (если есть) мог бы помочь здесь. Моя цель состоит в том, чтобы достичь какого-то общего метода битвы, чтобы, если Огр встречает Огра, он использует битву Огр-против-Огре, но если Огр встречает Волшебника, он использует более общий метод. Кроме того, что если встречающиеся персонажи не имеют общих черт? Как мы можем решить, кто выиграет битву?

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

Ответы [ 13 ]

19 голосов
/ 05 мая 2010

шаблон посетителя"- это способ отделить алгоритм от структуры объекта, с которой он работает".

Для вашего примера, вы можете иметь

class Character {
    boolean battle(BattleVisitor visitor) {
       return visitor.visit(this);
    }
}

class Ogre extends Character {..}
class Wizard extends Character {..}
class Dwarf extends Character {..}

interface BattleVisitor {
    boolean visit(Ogre character);
    boolean visit(Wizard character);
    boolean visit(Dwarf character);
}

class OgreBattleVisitor implements BattleVisitor {
    private Ogre ogre;
    OgreBattleVisitor(Ogre ogre) { this.ogre = ogre; }
    boolean visit(Ogre ogre) {
      // define the battle 
    }

    boolean visit(Wizard wizard) {
      // define the battle 
    }
    ...
}

И всякий раз, когда происходит бой:

targetChar.battle(new OgreBattleVisitor(ogre));

Определите реализацию Visitor для Wizard и Dwarf и всего, что появляется. Также обратите внимание, что я определяю результат метода visit как boolean (выиграл или проиграл), а не возвращаю победителя.

Таким образом, при добавлении новых типов вам нужно будет добавить:

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

Теперь получается, что у вас будет некоторое дублирование кода на случай, если "Ogre vs Wizard" == "Wizard vs Ogre". Я не знаю, так ли это - например, может быть разница в зависимости от того, кто наносит первый удар. Кроме того, вы можете предложить совершенно другой алгоритм, скажем, «Болотная битва с людоедом» по сравнению с «деревенской битвой с людоедом». Таким образом, вы можете создать нового посетителя (или иерархию посетителей) и применять подходящего при необходимости.

7 голосов
/ 05 мая 2010

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

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

Например, что, если "показатель битвы" огра был рассчитан по его росту плюс вес, умноженный на его длину топора, а показатель волшебника был рассчитан по его возрасту, умноженному на его IQ?

abstract class Character {
    public abstract int battleScore();

    public Character battle(Character c1, Character c2) {
        (c1.battleScore() > c2.battleScore()) ? return c1 : c2;
    }
}

class Ogre extends Character {
    public int battleScore() {
        return (height + weight) * axeLength;
    }
 }

 class Wizard extends Character {
    public int battleScore() {
        return height + (age * IQ);
    }
 }
5 голосов
/ 05 мая 2010

Звучит так, как вы хотите двойная отправка .

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

4 голосов
/ 05 мая 2010

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

Battle(Ogre ogre, Wizard wizard)

Который вернул бы объект, содержащий победителя (или самого победителя, что угодно). Это отделит логику битвы от бойцов, а также позволит вам генерировать, то есть:

Battle(Creature creat1, Creature creat2)

Был бы запасным методом для любых пар существ (если предположить, что у Волшебника / Огра / и т. Д. Все есть «Существо» в качестве базового класса), у которого нет определенной логики. Это позволит вам добавлять / редактировать / удалять логику боя, не изменяя ни одно из существ.

3 голосов
/ 05 мая 2010

Это именно такие проблемы Стратегия - это цель, которую нужно решить.

Давайте рассмотрим части Strategy

  • Контекст: борьба
  • Стратегия: Как Вы бы определили, кто победит
  • Конкретная стратегия: где борьба имеет место, и решение принято.

Таким образом, вместо того, чтобы передать эту ответственность самому персонажу (потому что они всегда будут говорить, «Я выиграл !!, нет, я выиграл, нет, я ...» ), вы можете создать RefereeStrategy.

Конкретная реализация решит, кто победит.

стратегия в действии http://bit.ly/cvvglb

диаграмма, созданная с помощью http://yuml.me

Вы можете либо определить общие методы, на которые все персонажи соглашаются отвечать (что не похоже на то, что вы хотите, это полезно, когда все символы имеют одинаковые "атрибуты" или методы, такие как defense():int, attack():int, heal():int), или делать " слепая »стратегия.

Я делаю вторую («слепую» стратегию)

// All the contenders will implement this.
interface Character {
    public String getName();    
}
// The context
class FightArena {
    Character home;
    Character visitor;

    // The strategy 
    Referee referee;

    Character fight(){
        this.referee = RefereeFactory.getReferee( home.getName(), visitor.getName() );
        Character winner = referee.decideFightBetween( home, visitor );
        out.println(" And the winner iiiiss...... " + winner.getName() );
    }
}

interface Referee {
    Character decideFightBetween( Character one, Character two );
}

class RefereeFactory {

        static Referee getReferee( Character one, Character two ) {
             .... return the appropiate Refereee... 
        }    
}

// Concrete Referee implementation 
// Wizard biased referee, dont' trust him
class OgreWizardReferee implements Referee {
    Character decideFightBetween( Character one, Character two ) {
        if( one instanceof Wizard ){
            return one;
        }else{
            return two;
        }
    }
}
class OgreReferee implements Referee {
    Character decideFightBetween( Character one, Character two ) {
        Ogre a = ( Ogre ) one;
        Ogre b = ( Ogre ) two;

        if( a.height > b.height || a.axeLength > a.axeLength ) {
            return a;
        }
        return b;
    }

}

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

Он сохраняет контекст (вашу боевую арену) свободным от конструкций if / elseif / else / if / else, передавая рефери решение победителя, и изолирует ваших разных персонажей от каждого Другой.

3 голосов
/ 05 мая 2010

Я думаю, вы должны переосмыслить все это.

Давайте просто возьмем World of Warcraft в качестве примера того, как можно сражаться, просто потому что это известная игра.

У вас есть несколько разных классов, которые способны делать разные вещи, и имеют свои сильные и слабые стороны. Тем не менее, все они имеют некоторые общие типы статистики. Например, у Мага больше интеллекта, чем у Воина, но у Воина будет гораздо больше Силы, чем у Мага.

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

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

Но вот еще кое-что, что следует учитывать: возможно, вам не следует использовать класс для каждого типа. Было бы предпочтительнее, если бы вы могли использовать одну и ту же формулу для всех - даже если у них нет одинакового набора способностей. Вместо того чтобы кодировать каждую способность, у вас будет просто список способностей и их параметров в файле, и класс Character будет использовать его для выполнения всех этих вычислений. Это облегчает настройку формулы (только в одном месте для поиска) и упрощает настройку способностей (просто измените файл). Писать эту формулу немного сложнее, потому что вы, возможно, захотите дать Огре бонус за высокую силу, тогда как Волшебник получит бонус за высокий интеллект, но это лучше, чем иметь Х почти идентичных формул, по одной на каждую stat, который может повлиять на вывод.

2 голосов
/ 05 мая 2010

Один из способов сделать это - создать новый интерфейс для всех типов символов, таких как

public interface Fightable
{
    public Fightable doBattle(Fightable b);
}

И тогда вы будете реализовывать doBattle в каждом классе. Например, в классе Ogre вы можете проверить, был ли b экземпляром Ogre (в этом случае сделать одно), Wizard (в этом случае другое) и т. Д ...

Проблема заключается в том, что каждый раз, когда вы добавляете новый тип, вам нужно добавлять код для каждого отдельного класса символов для каждого другого символа, что не особенно поддерживаемо. Кроме того, вам следует обратить внимание на то, чтобы убедиться, что операции выполнялись должным образом, т. Е. Если вы изменили метод doBattle в классе Ogre относительно Wizards, но не в классе Wizard относительно Ogres, вы можете столкнуться с ситуацией результат зависит от того, вызываете ли вы anOgre.doBattle (aWizard) или aWizard.doBattle (anOgre).

Что может быть лучше, так это создать класс Битвы, который принимает два персонажа и содержит логику для боя, видя, какие два типа классов были ему переданы: таким образом, вам нужно только менять свой класс Битвы каждый раз, когда новый тип персонажа добавлен! Вы хотите инкапсулировать поведение, которое наиболее вероятно изменится чаще всего.

1 голос
/ 05 мая 2010

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

Если оно имеет более высокое значение, чем то, с которым оно борется, оно выигрывает.

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

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

Значение монстра может быть рассчитано на основе его атаки и соответствующей брони врагов, а также HP и т. Д. И т. Д. И т. П.

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

1 голос
/ 05 мая 2010

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

Не слушайте весь этот пух о шаблонах дизайна ...

0 голосов
/ 10 мая 2010

Я знаю, что это немного поздно, но Стив Йегге несколько лет назад написал статью почти об этой точной проблеме (он даже использовал пример игры!).

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