Как реализовать команды чата в виде отдельных классов? - PullRequest
0 голосов
/ 11 января 2019

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

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

class Command()
{
    string keyword;
    int globalCooldown;
    List<string> args;
}

class DoStuffA : Command(List<string> _args)
{
    keyword = "pleasedostuffa";
    globalCooldown = 2;
    args = _args;

    DoStuff(List<string> args)
    {
      //doing stuff A here with all the logic and so on
    }
}
class DoStuffB : Command(List<string> _args)
{
    keyword = "pleasedostuffb";
    globalCooldown = 8;
    args = _args;

    DoStuff(List<string> args)
    {
      //doing stuff B here with all the logic and so on
    }
}

Зачем мне это нужно, потому что я хочу сохранить все возможные команды в List<Commands>, и когда появится новое сообщение чата, найдите, какой объект из этого списка соответствует keyword с командой chat, и выполните соответствующую функцию , Например, если кто-то публикует !pleasedostuffa, я выполняю

foreach (Command c in commands)//commands is List<Command>
{
    if(c.keyword==receivedCommand.command)//.command is string
    {
        c.DoStuff(receivedCommand.argsAsList)//.argsAsList is List<string>
    }
}

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

Заранее спасибо!

1 Ответ

0 голосов
/ 11 января 2019

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

public abstract class Command
{
    public string keyword;
    public int globalCooldown;
    //List<string> args;

    public abstract void DoStuff(List<string> args);
}

public class DoStuffA : Command
{
    //public string keyword = "pleasedostuffa";
    //public int globalCooldown = 2;
    //args = _args;

    public DoStuffA()
    {
        keyword = "pleasedostuffa";
        globalCooldown = 2;
    }

    public override void DoStuff(List<string> args)
    {
      //doing stuff A here with all the logic and so on
    }
}

public class DoStuffB : Command
{
    //public string keyword = "pleasedostuffb";
    //public int globalCooldown = 8;
    // args = _args;

    public DoStuffB()
    {
        keyword = "pleasedostuffb";
        globalCooldown = 8;
    }

    public override void DoStuff(List<string> args)
    {
      //doing stuff B here with all the logic and so on
    }
}

Итак, пара заметок.

Метод наследования

Здесь я делаю базовый класс абстрактным, просто чтобы убедиться, что каждая дочерняя команда реализует абстрактную функцию DoStuff(). В конце концов, что бы вы сделали с экземпляром базового класса? Это ничего не сделает, потому что у вас нет реальной реализации. Таким образом, абстрактный помогает избежать случайного создания экземпляра самой команды, а также обеспечивает правильную работу реализаторов подтипов.

Во-вторых, на уровне дочернего класса вам необходимо override метод базового класса. Это гарантирует, что все, что вызывает ((Command)doStuffB).DoStuff(), получит правильную реализацию функции.

Теперь, когда у вас есть метод DoStuff () для Command, ваш цикл foreach должен работать так, как вы ожидаете. У вас есть метод, доступный в базовом классе, поэтому виртуальные переопределения на дочернем уровне можно запускать без приведения.

Доступ к базовому классу

Поля, которые вы пытаетесь объявить здесь, keyword и globalCooldown, - это не то, как люди обычно предоставляют такую ​​информацию, но прежде чем мы перейдем к этому, я собираюсь объяснить более фундаментальный принцип доступа члены базового класса из унаследованных классов.

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

Как видите, я закомментировал поля в дочерних классах. Если вы объявите их там, они будут скрывать (но не переопределять) элементы с одинаковыми именами в базовом классе. Для полей нет эквивалента virtual или abstract, поэтому вам нужна другая стратегия. Здесь мы оставляем исходное объявление этих полей в базовом классе, чтобы они были доступны любому объекту, содержащему любой тип команды. Но вместо того, чтобы перемаркировать их на уровне дочернего класса, мы просто устанавливаем значения членов базового класса в конструкторе для дочерних классов.

Обратите внимание, что для ясности, вы можете явно указать, что вы устанавливаете член в базовом классе, используя вместо этого base.keyword = "etc";.

Предоставление внутренних значений через свойства

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

public string Keyword // properties start with a capital letter by convention
{
    get; // The get accessor is public, same as the overall property
    protected set; // the set accessor is protected
}

Метод доступа protected означает, что он все еще доступен для установки дочерними классами, но не кем-либо еще. Это, вероятно, то, что вы хотите. Итак, теперь в вашем дочернем конструкторе вы можете установить base.Keyword = "whatever";, и ваш код foreach может ссылаться, но не перезаписывать это значение. Вы можете объявить GlobalCooldown аналогичным образом.

...