统一函数入口和参数的设计模式相关内容

 

本文在 coze 的帮助下实现

统一函数入口的设计模式实践

简化的命令模式/映射表(map-based dispatch)的实用模式

需求1:如果是多个模块的多个不同函数,统一在另一个模块中调用,那么该如何统一多个模块的不同函数的入参?他们有1个公共参数和其他的不同的参数。

// 假设这是不同模块暴露出来的函数
import { functionA } from 'moduleA';
import { functionB } from 'moduleB';
import { functionC } from 'moduleC';

// 创建一个映射对象,按模块名和函数名来保存函数
const functionMap = {
  moduleA: { functionA },
  moduleB: { functionB },
  moduleC: { functionC },
  // 其他模块和函数...
};

// 创建统一的调用函数
function callFunction(moduleName, functionName, commonParam, additionalParams) {
  // 检查模块和函数是否存在
  if (functionMap[moduleName] && typeof functionMap[moduleName][functionName] === 'function') {
    // 调用相应的函数
    functionMap[moduleName][functionName](commonParam, additionalParams);
  } else {
    throw new Error(`函数 ${moduleName}.${functionName} 不存在`);
  }
}

// 调用函数示例
callFunction('moduleA', 'functionA', '共同参数', {param: 'value'});
callFunction('moduleB', 'functionB', '共同参数', {anotherParam: 'anotherValue'});
callFunction('moduleC', 'functionC', '共同参数', {yetAnotherParam: 'yetAnotherValue'});

简化版本的命令模式,因为它的目的是将请求动作封装为一个可以被存储、传输和调用的对象。

也可以称为映射表(map-based dispatch)的实用模式是一种编程技巧,它通过使用映射对象(例如字典、哈希表等)将函数名(或者其他可以用作键的标识符)与实际要执行的函数进行映射。应用包括:

  1. 命令分发
  2. 事件处理
  3. 状态机实现

优点:

  1. 减少if/else或switch语句
  2. 扩展性好
  3. 分发快速 使用这种模式时,你需要注意确保映射表正确认定了所有的可能性,并且应当包括如何处理映射中不存在的键的策略(例如,抛出错误或执行默认操作)。

命令模式 (Command Pattern)

命令模式是一种行为设计模式,它可将请求转换为一个包含与请求相关的所有信息的独立对象。从而允许使用不同的请求、队列或日志请求,并提供撤销操作的能力。命令模式会将执行操作的对象和知道如何执行该操作的对象解耦

应用场景:

  • 需要参数化对象根据不同的请求来执行动作时。
  • 需要排队执行的操作,或者需要支持撤销/重做操作时。
  • 想要将操作记录到日志中或者是想要可以持久化操作的历史记录时。

命令模式通常涉及以下角色:

  1. 命令(Command)接口:声明执行操作的方法。
  2. 具体命令(Concrete Command)类:实现命令接口的具体操作,持有接收者对象的引用。
  3. 接收者(Receiver)类:知道如何实施与执行一个请求相关的操作;任何类都可能作为一个接收者。
  4. 请求者(Invoker)类:负责调用命令对象执行请求,通常会持有命令对象,但不直接执行工作。
  5. 客户端(Client)类:创建一个具体命令对象,并设置其接收者。
// 接收者
class Light {
  turnOn() {
    console.log('The light is on');
  }
  
  turnOff() {
    console.log('The light is off');
  }
}

// 命令
class LightOnCommand {
  constructor(light) {
    this.light = light;
  }

  execute() {
    this.light.turnOn();
  }
}

class LightOffCommand {
  constructor(light) {
    this.light = light;
  }

  execute() {
    this.light.turnOff();
  }
}

// 请求者
class RemoteControl {
  setCommand(command) {
    this.command = command;
  }
  
  pressButton() {
    this.command.execute();
  }
}

// 客户端代码
const light = new Light();
const lightOnCommand = new LightOnCommand(light);
const lightOffCommand = new LightOffCommand(light);

const remote = new RemoteControl();

// 开灯
remote.setCommand(lightOnCommand);
remote.pressButton();

// 关灯
remote.setCommand(lightOffCommand);
remote.pressButton();

适配器模式

需求2:如果目标是统一不同模块中各接口参数的调用逻辑,而不是直接关注于调用过程本身,我们可以通过设计一个中间层或者适配器模式来实现这个目标。

适配器模式

它允许将一个类的接口转换成客户端期待的另一个接口。适配器模式使得原本因接口不兼容而不能一起工作的类可以一起工作。

实现步骤

  1. 定义统一的调用接口:首先定义一个统一的调用接口,这个接口规定了调用逻辑的基本框架,包括接收一个公共参数以及一个包括其他所有参数的对象。
  2. 实现适配器函数:对于每个模块的接口,分别实现一个适配器函数。这个适配器函数遵循上一步中定义的统一接口,内部根据具体的接口实现调整参数并调用实际的函数。
  3. 创建中间层或库:在一个中间层或库中集中管理这些适配器函数,以方便外部调用和管理。
// 假设的模块A和模块B的接口
function interfaceFromModuleA(commonParam, specificParamA) {}
function interfaceFromModuleB(commonParam, specificParamB1, specificParamB2) {}

// 定义统一的调用逻辑接口(适配器函数)
function adapterForModuleA(commonParam, params) {
    // 适配器逻辑,从params中提取interfaceFromModuleA需要的参数
    interfaceFromModuleA(commonParam, params.specificParamA);
}

function adapterForModuleB(commonParam, params) {
    // 适配器逻辑,从params中提取interfaceFromModuleB需要的参数
    interfaceFromModuleB(commonParam, params.specificParamB1, params.specificParamB2);
}

// 统一的中间层管理
const adapterManager = {
    call(moduleName, commonParam, params) {
        switch (moduleName) {
            case 'ModuleA':
                adapterForModuleA(commonParam, params);
                break;
            case 'ModuleB':
                adapterForModuleB(commonParam, params);
                break;
            // 添加更多模块的处理逻辑
            default:
                throw new Error('Unsupported module');
        }
    }
}

// 调用示例
adapterManager.call('ModuleA', 'commonParam', { specificParamA: 'valueA' });
adapterManager.call('ModuleB', 'commonParam', { specificParamB1: 'valueB1', specificParamB2: 'valueB2' });

谈一谈 TS 的 Options 模式

在TypeScript中,选项模式(Options Pattern)通常是指通过一个对象(称为选项对象)将函数或构造函数的参数组织起来,而非单独传递参数,这样当参数数量多或者部分参数可选时,可以提高代码的可读性和可维护性。

interface ServerOptions {
  host: string;
  port: number;
  timeout?: number; // 可选参数
  ssl?: boolean; // 可选参数
}

class Server {
  options: ServerOptions;

  constructor(options: ServerOptions) {
    // 设置默认值
    this.options = {
      timeout: 5000, // 默认超时5秒
      ssl: false, 
      ...options // 使用传入的选项覆写默认值
    };
  }

  start() {
    // 使用选项中的配置启动服务器
    console.log(`Starting server at ${this.options.host}:${this.options.port}`);
    if (this.options.ssl) {
      console.log('Starting with SSL/TLS encryption');
    }
    if (this.options.timeout) {
      console.log(`Setting timeout to ${this.options.timeout} milliseconds`);
    }
    // 更多启动逻辑...
  }
}

// 使用示例

// 有明确指定host和port的选项对象
let serverOptions: ServerOptions = {
  host: 'localhost',
  port: 8080
};

// 创建Server实例,通过选项对象传参
let server = new Server(serverOptions);

// 启动服务器
server.start();

在 JS 中利用可选的对象属性来实现

class Server {
  constructor(options) {
    this.options = {
      // 默认值
      timeout: 5000,
      ssl: false,
      ...options // 使用给定的选项覆盖默认值
    };
  }

  start() {
    console.log(`Starting server at ${this.options.host}:${this.options.port}`);
    if (this.options.ssl) {
      console.log('Starting with SSL encryption');
    }
    if (this.options.timeout) {
      console.log(`Setting timeout to ${this.options.timeout} milliseconds`);
    }
  }
}

// 使用示例
const serverOptions = {
  host: 'localhost',
  port: 8080
};

const server = new Server(serverOptions);
server.start();