Express тип композиции в OOP - PullRequest
2 голосов
/ 01 марта 2020

Предположим, мы хотим написать RPG-игру с тремя основными типами персонажей: Fighter, Mage и Archer. Также мы объединили типы персонажей, такие как Рыцарь, Паладин и Рейнджер. В TypeScript мы можем описать интерфейсы для символов следующим образом:

    // fighter can fight
    // mage can cast
    // archer can shoot
    // paradin can cast and fight
    // knight can fight and shoot
    // ranger can cast and shoot

    type Fighter = {fight: () => string}
    type Mage = {cast: () => string}
    type Archer = {shoot: () => string}
    type Knight = Fighter & Archer
    type Paladin = Fighter & Mage
    type Ranger = Archer & Mage

Вопрос в том, как реализовать эти интерфейсы, используя только методы OOP (представьте, что мы работаем с основным OOP языком, таким как C# )?

У меня не получилось выразить это с наследованием без дублирования поведения. В настоящее время я вижу только один способ:

class Paladin {
  constructor(private fighter: Fighter, private mage: Mage) {}

  fight() {this.fighter.fight()}
  cast() {this.mage.cast()}
}

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

Ответы [ 3 ]

2 голосов
/ 01 марта 2020

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

public class Hero
{
    private readonly string name;
    private readonly IEnumerable<IHeroAbility> abilities;

    public Hero(string name, [Optional] IEnumerable<IHeroAbility> abilities)
    {
        this.name = name;
        this.abilities = abilities ?? new List<IHeroAbility> { new OhCrap() };
    }

    public void Attack()
    {
        Console.WriteLine(name);

        foreach (var ability in abilities) ability.Execute();

        Console.WriteLine();
    }
}

Теперь подготовьте доступные способности:

public interface IHeroAbility
{
    void Execute();
}

public class SwingSwordAbility : IHeroAbility
{
    public void Execute()
    {
        Console.WriteLine("And with all his might the hero swung his sword down onto his opponent...");
    }
}

public class ShootArrowAbility : IHeroAbility
{
    public void Execute()
    {
        Console.WriteLine("The hero loads an arrow into his bow without blinking and launches it with fierce speed towards his opponant...");
    }
}

public class CastFireBallAbility : IHeroAbility
{
    public void Execute()
    {
        Console.WriteLine("The hero reaches deep into the underverse and hurls a ball of fire towards his opponant...");
    }
}

public class OhCrap : IHeroAbility
{
    public void Execute()
    {
        Console.WriteLine("The hero pats his pockets as if he forgot something at home...");
    }
}

Затем составьте типы ваших героев:

class Program
{
    static void Main(string[] args)
    {
        IHeroAbility swingSwordAbility = new SwingSwordAbility();
        IHeroAbility shootArrowAbility = new ShootArrowAbility();
        IHeroAbility castFireBallAbility = new CastFireBallAbility();

        var fighter = new Hero("The Fighter", new[] { swingSwordAbility });
        fighter.Attack();

        var mage = new Hero("The Mage", abilities: new[] { castFireBallAbility });
        mage.Attack();

        var archer = new Hero("The Archer", abilities: new[] { shootArrowAbility });
        archer.Attack();

        var knight = new Hero("The Knight", abilities: new[] { swingSwordAbility, shootArrowAbility });
        knight.Attack();

        var paladin = new Hero("The Paladin", abilities: new[] { swingSwordAbility, castFireBallAbility });
        paladin.Attack();

        var ranger = new Hero("The Ranger",abilities: new[] { shootArrowAbility, castFireBallAbility });
        ranger.Attack();

    }
}

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

public class Hero
{
    private readonly string name;
    private readonly IEnumerable<IHeroAbility> meleeAbilities;
    private readonly IEnumerable<IHeroAbility> magicAbilities;
    private readonly IEnumerable<IHeroAbility> rangedAbilities;

    public Hero(
        string name,
        [Optional] IEnumerable<IHeroAbility> meleeAbilities,
        [Optional] IEnumerable<IHeroAbility> magicAbilities,
        [Optional] IEnumerable<IHeroAbility> rangedAbilities)
    {
        this.name = name;

        var defaultAbilities = new List<IHeroAbility> { new OhCrap() };
        this.meleeAbilities = meleeAbilities ?? defaultAbilities;
        this.magicAbilities = magicAbilities ?? defaultAbilities;
        this.rangedAbilities = rangedAbilities ?? defaultAbilities;
    }

    public void MeleeAttack()
    {
        var ability = meleeAbilities.First();
        Console.WriteLine(name);
        ability.Execute();
    }

    public void MagicAttack()
    {
        var ability = magicAbilities.First();
        Console.WriteLine(name);
        ability.Execute();
    }

    public void RangedAttack()
    {
        var ability = rangedAbilities.First();
        Console.WriteLine(name);
        ability.Execute();
    }

}

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

Надеюсь, это поможет!

1 голос
/ 02 марта 2020

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

Использование Композиция (здесь Переадресация ) считается более сильной схемой, чем та, которая использует наследование (см. Композиция над наследованием ) но может показаться менее естественным , поэтому ИМО вы сказали:

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

"Тот же человек" семантически более связан с наследованием чем состав но приводит к более хрупкому дизайну. Мышление с точки зрения ролей или способностей (*) более гибкое: Целитель , Друид - это не человек, а способность, которую может иметь человек / игрок / персонаж.

(*) Термин взят из ответа Альберта но я все же предпочитаю ваше решение гораздо проще и яснее. Чтобы go далее, нам нужен список эталонных сценариев ios и / или работающего прототипа (или любой существующей программы) для оценки дизайна и кода ...

0 голосов
/ 02 апреля 2020

Для этой цели я рекомендую использовать " Шаблон дизайна декоратора ".

, после которого вы можете сделать своего главного героя добавив к ним определенное c поведение с декораторами. например:

var  Fighter = new Charactermaker();
var  Mage  = new Charactermaker();
var  Archer = new Charactermaker();

KnifeDecorator addKnight = new KnifeDecorator(Fighter);
Paladin Decorator addPaladin= new PaladinDecorator(addKnight);

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

для получения дополнительной информации вы можете увидеть текущую ссылку:

Шаблон оформления декоратора

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