Подтвердить что ты не робот

Использование наследования и полиморфизма для решения общей игровой задачи

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

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

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

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

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;
}

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

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

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;
  }
}

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

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

4b9b3361

Ответ 1

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

В вашем примере вы можете иметь

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));

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

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

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

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

Ответ 2

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

Я не знаю, является ли это подходящей альтернативой, но что, если вы пошли на совершенно другую схему и приписывали "битву" каждому персонажу вместо того, чтобы полагаться на сравнение отдельных черт. Вы можете использовать атрибуты символов в формуле, чтобы дать некоторое целое число, которое можно сравнить с другим персонажем. Затем вы можете использовать общий метод 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);
    }
 }

Ответ 3

Похоже, вы хотите двойная отправка.

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

Ответ 4

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

Battle(Ogre ogre, Wizard wizard)

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

Battle(Creature creat1, Creature creat2)

Являлся бы резервным методом для любого создания существ (предполагая, что Wizard/Ogre/etc имеют "Существо" в качестве базового класса), который не имеет конкретной логики. Это позволит вам добавлять/редактировать/удалять логику боя без изменения самих существ.

Ответ 5

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

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

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

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

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

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

Ответ 6

Это именно те проблемы, которые Strategy нацелен на решение.

Рассмотрим части 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", пересылая судье решение победителя и изолировать ваши разные символы от друг друга.

Ответ 7

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

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

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

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

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

Ответ 8

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

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

Ответ 9

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

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

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

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

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

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

Ответ 10

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

Ответ 11

Что-то вроде этого?

class Trait
{
    enum Type
    {
        HEIGHT,
        WEIGHT,
        IQ
    }
    protected Type type;
    protected int value;

    public Trait(int value, Type type)
    {
        this.type = type;
        this.value = value;
    }

    public boolean compareTo(Trait trait)
    {
        if(trait.type != this.type)
            throw new IllegalArgumentException(trait.type+" and "+this.type+" are not comparable traits");
        else
            return this.value - trait.value;
    }
}

class Character
{
    protected Trait[] traits;

    protected Character(Trait[] traits)
    {
        this.traits = traits;
    }

    public Trait getTrait(Trait.Type type)
    {
        for(Trait t : traits)
            if(t.type == type) return t;
        return null;
    }

    public Character doBattleWith(Character that)
    {
        for(Trait thisTrait : traits)
        {
            otherTrait = that.getTrait(thisTrait.type);
            if(otherTrait != null)
            {
                int comp = thisTrait.compareTo(otherTrait);

                if(comp > 0)
                    return this;
                else if (comp < 0)
                    return that;
            }
        }
        return null;
    }
}

class Ogre extends Character
{
    public Ogre(int height, int weight)
    {
        super(new Trait[]{
            new Trait(Type.HEIGHT,height),
            new Trait(Type.WEIGHT,height)});
    }
}

Ответ 13

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