如何动态加载具体实现并参数化对象创建? 示例

问题描述

上下文

我正在构建一个可以接收命令的Chrome扩展程序,该扩展程序应基于TypeScript。我想将类的具体实现提取到它自己的文件中,该文件可以动态加载。

Typescript dynamic loading command pattern

示例

在我的chrome扩展程序中,我收到以下JSON。

{
    "action": "get_tasking","tasks": [
        {
            "command": "ConcreteContentCommand","parameters": "{'param1': 'bla','param2': 'bla2'}","timestamp": 1578706611.324671,//timestamp provided to help with ordering
            "id": "task uuid",}
    ]
}

首先,我将从IndexedDB中加载适当的文件,该文件可以命名为 ConcreteContentCommand.js ,其中包含 ConcreteContentCommand -实现。 现在,我想使用参数 param1 param2 创建 ConcreteContentCommand 的实例,然后供我的调用使用>。

class ContentReceiver {
  targetURL: string;
  payload: string;

  constructor(targetURL: string,payload: string) {
    this.targetURL = targetURL;
    this.payload = payload;
  }

  InjectRunnable() {
    // inject the payload into tab
    console.log(`Do something with ${this.payload}`);
  }
}

interface Command {
  execute(): void;
}

abstract class ContentCommand implements Command {
  receiver: ContentReceiver;

  protected constructor(url: string) {
    this.receiver = new ContentReceiver(url,this.runnable.toString());
  }

  abstract runnable(): void;

  execute() {
    this.receiver.InjectRunnable();
  }
}

abstract class BackgroundCommand implements Command {
  abstract runnable(): void;

  execute() {
    this.runnable();
  }
}

// This class shall be extracted,my extension will not kNow about this class
class ConcreteContentCommand extends ContentCommand {
  param1: string;
  param2: string;

  constructor(param1: string,param2: string) {
    super("https://staticurl");
    this.param1 = param1;
    this.param2 = param2;
  }

  runnable() {
    // Do Concrete Stuff here
  }
}

我必须执行哪些选择?

解决方法

以UML开头的提示!

在命令模式的理想情况下,您可以执行任何命令而无需知道命令是什么或哪些命令可用,因为每个命令都是一个自包含的对象。但是,如果要基于字符串化的数据创建命令实例,则有时必须将字符串名称映射到其类实现。

您在UML中提到了CommandListener。我真的不知道如何在服务器上执行所有这些操作,但是如果每个ConcreteCommand都可以附加自己的侦听器(侦听所有命令并处理与其名称匹配的命令),则可能是理想的选择。 / p>

另一个想法是拥有一个集中的TaskParser来响应所有JSON操作,方法是将它们转换为Command并将它们分配给调用方(在这里我称为CommandRunner) 。每个ConcreteCommand必须向TaskParser注册。

在考虑要从JSON字符串创建Command所需的条件时,我首先想到了一个通用的StringableCommand,它取决于参数Args。它具有name用于标识,可以由Args构造,并且具有用作type guard的方法validateArgs。如果validateArgs返回true,则已知args数组的类型为Args,可以安全地调用new

interface StringableCommand<Args extends any[]> extends Command {
    new(...args: Args): Command;
    name: string;
    validateArgs( args: any[] ): args is Args;
}

请注意,此接口适用于类原型,而不适用于类实例(那部分让我有些不安),因此我们不必说我们的类extends StringableCommand。我们必须将validateArgs作为static方法来实现,以使其在原型中可用。我们不需要显式实现name,因为它 already exists

然而,StringableCommand中包含泛型会使我们的TaskParser感到困惑,因为fromArgs将包含许多不同类型的命令。因此,我认为最好使用方法Command定义一个可构造的命令,该方法接受一个数组,该数组接受任何/未知参数,并返回Error或引发null(您可以返回undefinedinterface StringableCommand { name: string; fromArgs( ...args: any[] ): Command; // or throw an error } )。该类可以在内部处理验证,也可以依靠构造函数应抛出错误的事实(但我们必须告诉Typescript忽略它)。

interface ActionPayload {
    action: string;
    tasks: TaskPayload[];
}

interface TaskPayload {
    command: string;
    parameters: any[];
    timestamp: number;
    id: string;
}

我们正在处理解析为以下内容的JSON字符串:

parameters

我将您的TaskParser更改为数组而不是键对象,以便更轻松地将它们作为参数传递给命令的构造函数。

我们用于解决这些命令的class TaskParser { private readonly runner: CommandRunner; private commandConstructors: Record<string,StringableCommand> = {} private errors: Error[] = []; constructor(runner: CommandRunner) { this.runner = runner; } public registerCommand( command: StringableCommand ): void { this.commandConstructors[command.name] = command; } public parseJSON( json: string ): void { const data = JSON.parse(json) as ActionPayload; data.tasks.forEach(({command,parameters,id,timestamp}) => { const commandObj = this.findCommand(command); if ( ! commandObj ) { this.storeError( new Error(`invalid command name ${command}`) ); return; } try { const c = commandObj.fromArgs(parameters); this.runner.addCommand(c,timestamp,id); } catch (e) { // catch errors thrown by `fromArgs` this.storeError(e); } }); } private findCommand( name: string ): StringableCommand | undefined { return this.commandConstructors[name]; } private storeError( error: Error ): void { this.errors.push(error); } } 类看起来像这样:

CommandRunner

需要一个class CommandRunner { addCommand( command: Command,timestamp: number,id: string ): void { // whatever args you need // do something } } ,它现在只是一个存根,因为我不知道您的实现细节,但这是您调用/执行命令的地方。

^.*?/([^/]*)/?$

Typescript Playground Link

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...