问题描述
上下文
我正在构建一个可以接收命令的Chrome扩展程序,该扩展程序应基于TypeScript。我想将类的具体实现提取到它自己的文件中,该文件可以动态加载。
示例
在我的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
(您可以返回undefined
或interface 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
}
}
,它现在只是一个存根,因为我不知道您的实现细节,但这是您调用/执行命令的地方。
^.*?/([^/]*)/?$