如何将聊天命令实现为单独的类?

How to implement chat commands as separate classes?

我目前正在为 Twitch 频道开发聊天机器人,我希望程序中的所有命令都是独立的 classes,这样我只需要添加或删除一个 class 添加或删除命令。

我在互联网上搜索了很长时间,但一直没有找到合适的设计。下面是我认为它必须看起来像但找不到正确语法的内容。

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 匹配聊天命令并执行相应的功能。比如有人发!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>
    }
}

我希望我能正确解释这一点,并且真的很想至少知道这是如何实现的。

提前致谢!

您的方法设置几乎正确,但还需要进行一些其他更改。您需要让基础 class 将 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
    }
}

那么,一些注意事项。

方法继承

这里我将基础 class 抽象化,只是为了强制每个 child 命令实现抽象函数 DoStuff()。毕竟,您将如何处理基 class 的实例?它不会做任何事情,因为您没有实际的实现。因此抽象有助于避免意外实例化 Command 本身,并确保 sub-type 实现者做正确的事。

其次,在childclass层面,需要override基础class的方法。这确保任何调用 ((Command)doStuffB).DoStuff() 的东西都能正确实现函数。

现在您在 Command 上有了一个 DoStuff() 方法,您的 foreach 循环应该会按预期工作。您在基础 class 上有可用的方法,因此 child 级别的虚拟覆盖可以是 运行 而无需强制转换。

基础class成员访问

您要在此处声明的字段,keywordglobalCooldown,通常不是人们公开此类信息的方式,但在我们开始之前,我将解释一下从继承的 classes.

访问 base-class 成员的更基本原则

这两个字段需要标记为 public(并指定适当的类型),以便它们可以在 class 之外(在您的 foreach 中)使用。 public 关键字称为可访问性修饰符,还有一些其他可访问性选项,但在您的情况下,只有 public 可能会执行您想要的操作。

如您所见,我已经注释掉了 child class 中的字段。如果您在那里声明它们,它们将隐藏(但不会覆盖)基 class 上的同名成员。对于字段,没有 virtualabstract 的等价物,因此您需要另一种策略。在这里,我们将您对这些字段的原始声明保留在基础 class 上,以便它们可用于任何持有任何类型命令的对象。但是,我们没有在 child class 级别重新声明它们,而是简单地在 child class 的构造函数中设置基本 class 成员的值。

请注意,为了清楚起见,您可以使用 base.keyword = "etc"; 明确指定要在基础 class 上设置成员。

通过属性公开内部值

正如我指出的那样,这会起作用,但大多数人并不完全会公开 keywordglobalCooldown 值。为此,您通常会改用 属性。这使您可以存储和公开值,而不必冒让他人更改值(有意或无意)的风险。在这种情况下,您希望以这种方式声明 属性:

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 set 访问器意味着它仍然可以被 child classes 访问,但不能被其他任何人访问。这可能就是你想要的。所以现在,在您的 child 构造函数中,您可以设置 base.Keyword = "whatever"; 并且您的 foreach 代码可以引用但不能覆盖该值。您可以用类似的方式声明 GlobalCooldown。