利用策略模式优化不同参数函数调用的设计

利用策略模式优化不同参数函数调用的设计

针对JavaScript中根据不同业务场景调用参数数量可变的函数,本文介绍如何运用策略模式进行优雅设计。通过定义统一接口和具体策略类,实现动态选择并执行具有不同参数签名的函数,从而提高代码的灵活性、可维护性与扩展性,有效解决传统硬编码或条件判断导致的复杂性。

在软件开发中,我们经常会遇到需要根据特定条件执行不同操作的场景。当这些操作(函数)具有相似的职责但接受不同数量或类型的参数时,直接的条件判断或在查找表中存储函数引用可能会导致代码的复杂性增加,尤其是在调用时需要动态处理参数差异的情况下。本文将探讨如何利用策略模式(strategy pattern)优雅地解决这一问题,提升代码的结构性和可维护性。

问题背景与挑战

考虑一个招聘系统中的面试官验证逻辑。根据面试类型(例如技术面试 TECHNICAL_INTERVIEW 或 HR 面试 HR_INTERVIEW),验证函数 validateRecruiters 的参数签名可能不同。具体而言,HR 面试的验证可能需要额外的参数,而技术面试则不需要。

原始设计可能如下:

import { getHrRecruiters, getRecruiters } from '../queue';import { validateTechnicalInterview } from './validateTechnicalInterview';import { matchHrRecruiters } from './matchHrRecruiters';import { THrInterviewer, THrRecruit, TRecruit } from '../../types';export const recruitersCategoryHandlers = {  TECHNICAL_INTERVIEW: {    getter: getRecruiters,    setter: {      validateRecruiters: (        recruiters: THrInterviewer[],        recruit: TRecruit | THrRecruit      ) => validateTechnicalInterview(recruiters, recruit),    },  },  HR_INTERVIEW: {    getter: getHrRecruiters,    setter: {      validateRecruiters: (        recruiters: THrInterviewer[],        recruit: TRecruit | THrRecruit,        param3: any, // 额外参数        param4: any  // 额外参数      ) => matchHrRecruiters(recruiters, recruit as THrRecruit, param3, param4),    },  },};// 尝试调用时的困境// const matchedSlots = getMatchingSlots(//           recruitersCategoryHandlers[//             interviewCategory as EInterviewCategory//           ].setter.validateRecruiters(???), // 如何处理不同的参数?//           slotsWithEmail//         );

这种设计的问题在于,当尝试通过 interviewCategory 动态调用 validateRecruiters 时,由于不同类型下的 validateRecruiters 函数签名不一致,导致无法统一传递参数。我们不能简单地将所有可能的参数都传递给每一个 validateRecruiters 调用,因为这会造成类型不匹配或参数冗余。

策略模式简介

策略模式是一种行为型设计模式,它允许在运行时选择算法的行为。它定义了一系列算法,将每个算法封装起来,并使它们可以相互替换。策略模式让算法独立于使用它的客户端而变化。

在这个场景中,不同的面试类型(技术面试、HR面试)对应着不同的“验证策略”。通过策略模式,我们可以:

定义一个统一的验证接口,包含一个 validateRecruiters 方法。为每种面试类型创建具体的策略类,实现这个接口。在运行时,根据面试类型选择并使用相应的策略对象。

这种方法将参数差异的复杂性封装在各自的策略类中,使得客户端代码能够以统一的方式调用验证逻辑,而无需关心内部的参数处理细节。

解决方案:策略模式实践

1. 定义策略接口

首先,我们定义一个 ValidateRecruitersStrategy 接口,其中包含 validateRecruiters 方法。为了兼容不同数量的参数,我们使用剩余参数 (…params: any[]) 来接收可变数量的额外参数。

// 定义策略接口interface ValidateRecruitersStrategy {  validateRecruiters(recruiters: THrInterviewer[], recruit: TRecruit | THrRecruit, ...params: any[]): any;}

这个接口规定了所有具体策略类必须实现的方法签名。前两个参数 recruiters 和 recruit 是所有验证逻辑都需要的通用参数,而 …params 则用于捕获特定策略所需的额外参数。

2. 实现具体策略类

接下来,为每种面试类型创建具体的策略类,实现 ValidateRecruitersStrategy 接口。

技术面试策略: TechnicalInterviewStrategy 只使用前两个通用参数。

// 实现技术面试策略class TechnicalInterviewStrategy implements ValidateRecruitersStrategy {  validateRecruiters(recruiters: THrInterviewer[], recruit: TRecruit | THrRecruit): any {    // 实际调用内部的 validateTechnicalInterview 函数    return validateTechnicalInterview(recruiters, recruit);  }}

HR 面试策略: HrInterviewStrategy 会从 …params 中解构出其所需的额外参数。

// 实现HR面试策略class HrInterviewStrategy implements ValidateRecruitersStrategy {  validateRecruiters(recruiters: THrInterviewer[], recruit: TRecruit | THrRecruit, ...params: any[]): any {    // 从剩余参数中获取 HR 面试特有的 param3 和 param4    const [param3, param4] = params;    // 实际调用内部的 matchHrRecruiters 函数,并进行类型断言    return matchHrRecruiters(recruiters, recruit as THrRecruit, param3, param4);  }}

3. 整合策略到业务逻辑

现在,我们可以更新 recruitersCategoryHandlers 对象,使其存储 TechnicalInterviewStrategy 和 HrInterviewStrategy 的实例,而不是直接的函数定义。

// 使用策略模式重新定义 recruitersCategoryHandlersexport const recruitersCategoryHandlers = {  TECHNICAL_INTERVIEW: {    getter: getRecruiters,    setter: new TechnicalInterviewStrategy(), // 存储策略实例  },  HR_INTERVIEW: {    getter: getHrRecruiters,    setter: new HrInterviewStrategy(),     // 存储策略实例  },};

4. 动态调用策略

在客户端代码中,我们现在可以根据 interviewCategory 动态地获取相应的策略实例,并以统一的方式调用其 validateRecruiters 方法,传递所有可能的参数。策略实例会根据其内部逻辑决定使用哪些参数。

// 假设的上下文数据和参数const interviewCategory = 'HR_INTERVIEW'; // 或 'TECHNICAL_INTERVIEW'const param3 = 'some_hr_specific_value_A';const param4 = 'another_hr_specific_value_B';const currentRecruit = { /* ... 模拟 TRecruit 或 THrRecruit 数据 ... */ }; // 对应 recruit 参数const allSlots = { /* ... 模拟 slotsWithEmail 数据 ... */ }; // 对应 getMatchingSlots 的第二个参数// 1. 获取面试官列表 (通用步骤)const recruiters = recruitersCategoryHandlers[interviewCategory].getter();// 2. 调用验证策略,统一传递所有可能的参数const validationResult = recruitersCategoryHandlers[interviewCategory].setter.validateRecruiters(  recruiters,  currentRecruit,  param3,  param4 // 即使 TECHNICAL_INTERVIEW 不用,传递也无妨,策略内部会忽略);// 3. 使用验证结果进行后续操作// 假设 getMatchingSlots 函数的第一个参数是验证结果,第二个参数是所有槽位数据const matchedSlots = getMatchingSlots(validationResult, allSlots);console.log('匹配到的槽位:', matchedSlots);

完整示例代码

import { THrInterviewer, THrRecruit, TRecruit } from './types'; // 假设类型定义在 './types'// 模拟外部依赖函数const getRecruiters = () => [{ id: 'tech1', name: 'Tech Interviewer 1' }] as THrInterviewer[];const getHrRecruiters = () => [{ id: 'hr1', name: 'HR Interviewer 1' }, { id: 'hr2', name: 'HR Interviewer 2' }] as THrInterviewer[];const validateTechnicalInterview = (recruiters: THrInterviewer[], recruit: TRecruit | THrRecruit) => {  console.log('Executing Technical Interview Validation:', recruiters, recruit);  // 模拟验证逻辑  return recruiters.filter(r => r.name.includes('Tech'));};const matchHrRecruiters = (recruiters: THrInterviewer[], recruit: THrRecruit, param3: any, param4: any) => {  console.log('Executing HR Interview Matching:', recruiters, recruit, param3, param4);  // 模拟验证逻辑,使用 param3 和 param4  return recruiters.filter(r => r.name.includes('HR') && param3 === 'some_hr_specific_value_A');};const getMatchingSlots = (validatedRecruiters: THrInterviewer[], slots: any) => {    console.log('Getting matching slots with:', validatedRecruiters, slots);    // 模拟匹配槽位逻辑    return validatedRecruiters.map(r => ({ recruiterId: r.id, slot: '2023-10-27 10:00' }));};// 定义策略接口interface ValidateRecruitersStrategy {  validateRecruiters(recruiters: THrInterviewer[], recruit: TRecruit | THrRecruit, ...params: any[]): any;}// 实现技术面试策略class TechnicalInterviewStrategy implements ValidateRecruitersStrategy {  validateRecruiters(recruiters: THrInterviewer[], recruit: TRecruit | THrRecruit): any {    return validateTechnicalInterview(recruiters, recruit);  }}// 实现HR面试策略class HrInterviewStrategy implements ValidateRecruitersStrategy {  validateRecruiters(recruiters: THrInterviewer[], recruit: TRecruit | THrRecruit, ...params: any[]): any {    const [param3, param4] = params; // 从剩余参数中获取 HR 面试特有的参数    return matchHrRecruiters(recruiters, recruit as THrRecruit, param3, param4);  }}// 使用策略模式定义 recruitersCategoryHandlersexport const recruitersCategoryHandlers = {  TECHNICAL_INTERVIEW: {    getter: getRecruiters,    setter: new TechnicalInterviewStrategy(),  },  HR_INTERVIEW: {    getter: getHrRecruiters,    setter: new HrInterviewStrategy(),  },};// --- 客户端调用示例 ---// 场景一:技术面试const techInterviewCategory = 'TECHNICAL_INTERVIEW';const techRecruitData: TRecruit = { id: 'r1', name: 'Candidate A', skills: ['JS', 'React'] };const techAllSlots = { date: '2023-10-27', time: '10:00' };console.log('n--- 模拟技术面试场景 ---');const techRecruiters = recruitersCategoryHandlers[techInterviewCategory].getter();const techValidationResult = recruitersCategoryHandlers[techInterviewCategory].setter.validateRecruiters(  techRecruiters,  techRecruitData  // 额外参数在此处可以省略,因为 TechnicalInterviewStrategy 不会使用它们);const techMatchedSlots = getMatchingSlots(techValidationResult, techAllSlots);console.log('技术面试匹配到的槽位:', techMatchedSlots);// 场景二:HR 面试const hrInterviewCategory = 'HR_INTERVIEW';const hrRecruitData: THrRecruit = { id: 'r2', name: 'Candidate B', department: 'Sales' };const hrParam3 = 'some_hr_specific_value_A';const hrParam4 = 'another_hr_specific_value_B';const hrAllSlots = { date: '2023-10-28', time: '14:00' };console.log('n--- 模拟HR面试场景 ---');const hrRecruiters = recruitersCategoryHandlers[hrInterviewCategory].getter();const hrValidationResult = recruitersCategoryHandlers[hrInterviewCategory].setter.validateRecruiters(  hrRecruiters,  hrRecruitData,  hrParam3,  hrParam4);const hrMatchedSlots = getMatchingSlots(hrValidationResult, hrAllSlots);console.log('HR面试匹配到的槽位:', hrMatchedSlots);

注意事项

类型安全与 any 的使用: 策略接口中的 …params: any[] 提供了一定程度的灵活性,但也牺牲了部分类型安全。在 TypeScript 中,如果参数类型差异不大且数量固定,可以考虑使用函数重载来定义策略接口,或者使用更复杂的泛型约束来增强类型检查。然而,对于参数数量和类型都可能高度动态的场景,any[] 是一个实用的折衷方案。参数传递的统一性: 客户端在调用 validateRecruiters 时,应尽可能地传递所有可能需要的参数。具体策略类会根据自身逻辑选择性地使用这些参数。这种“多传无妨”的策略简化了客户端代码。可读性与可维护性: 策略模式将不同的验证逻辑封装在独立的类中,使得每个策略的职责单一,代码结构清晰。当需要新增面试类型或修改现有验证逻辑时,只需添加新的策略类或修改现有策略类,而无需改动核心调度逻辑,大大提高了系统的可扩展性和可维护性。替代方案: 对于更简单的场景,如果参数差异仅限于少数几个可选参数,也可以考虑在函数内部进行条件判断,或使用参数对象来传递所有参数,让函数自行选择。但当逻辑复杂性增加,且参数差异显著时,策略模式的优势会更加明显。

总结

通过引入策略模式,我们成功地解决了根据不同业务场景调用参数数量可变的函数所带来的挑战。策略模式通过将算法封装在独立的可替换对象中,使得客户端代码能够以统一的接口与不同的算法交互,从而提高了代码的灵活性、可维护性和可扩展性。这种设计模式在处理动态行为和多态性问题时非常有效,是构建健壮和可演进软件系统的重要工具

以上就是利用策略模式优化不同参数函数调用的设计的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1519105.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月20日 12:08:42
下一篇 2025年12月20日 12:09:01

相关推荐

  • 精准控制Vite + Svelte动态导入:实现条件式代码消除与优化

    本教程深入探讨在Vite和Svelte项目中如何实现条件式动态导入的代码消除。文章解释了动态导入默认的代码分割行为,并强调若要实现真正的死代码消除,必须依赖静态可分析的条件。通过详细讲解Vite的环境变量机制,教程提供了实用的代码示例,指导开发者确保只有实际执行的模块才被包含在最终构建产物中,从而有…

    2025年12月20日
    000
  • 优化Vite + Svelte中的条件动态导入:实现按需打包

    优化Vite + Svelte中的条件动态导入:实现按需打包 本文探讨vite和svelte项目中条件动态导入的打包行为,指出默认情况下,即使代码分支永不执行,相关模块也可能被包含在最终构建中。我们将深入解析打包器的工作原理,并提供两种有效策略:利用静态可分析条件和vite环境变量,以实现真正的按需…

    2025年12月20日
    000
  • Vite与Svelte中条件动态导入的优化策略与按需打包

    本文探讨了在Vite和Svelte项目中,如何优化条件动态导入以实现更精确的按需打包。尽管动态导入本身支持代码分割,但若希望未执行的代码分支在生产构建中被彻底移除(树摇),则需要确保条件语句可被静态分析。文章详细介绍了通过使用Vite环境变量等方式,实现 bundler 对条件分支的识别和优化,从而…

    2025年12月20日
    000
  • 掌握Vite中条件动态导入的死代码消除:基于静态分析的优化实践

    本文探讨了在Vite和Svelte项目中,如何优化条件动态导入以实现死代码消除。面对复杂条件导致未使用的模块仍被打包的问题,文章阐述了静态分析的重要性。通过利用Vite内置的环境变量机制,我们能够构建可被打包工具识别的条件,从而有效剔除生产环境中未被实际执行的动态导入模块,减小最终包体积。 理解条件…

    2025年12月20日
    000
  • JavaScript中按月份分组生成日期范围数组的教程

    本教程详细介绍了如何使用纯JavaScript,结合Intl.DateTimeFormat和Intl.PluralRules API,高效地在一个指定日期范围内生成一个按月份嵌套日期对象的数组。文章通过代码示例和详细解释,展示了如何实现日期分组和序数日期格式化,避免了对外部库的依赖。 在前端开发中,…

    2025年12月20日
    000
  • 生成指定日期范围内按月分组的日期数组

    本文将介绍如何使用原生 JavaScript 生成一个包含指定日期范围内所有日期的数组,并按月份进行分组。无需任何第三方库,即可实现类似 [May – month name, [1st May, 2nd May, 3rd May, …], June, [1st June, 2…

    2025年12月20日
    000
  • p5.js中函数首次调用耗时较长的原因分析与优化

    在使用p5.js进行图形绘制时,尤其是在WEBGL模式下,首次调用涉及图像处理的函数(如image())往往比后续调用耗时更多。这主要是因为首次调用时,p5.js需要为图像分配显存并进行纹理上传等初始化操作。理解这一机制有助于优化p5.js程序的性能。 深入理解p5.js的图像处理机制 在p5.js…

    2025年12月20日
    000
  • p5.js WebGL渲染性能探究:首次调用为何显著缓慢?

    本文深入探讨了p5.js在WEBGL模式下,首次调用image()函数渲染图像时性能显著慢于后续调用的现象。通过分析WebGL纹理分配与数据传输机制,揭示了图形内存分配、图像数据上传GPU等一次性开销是主要原因。文章还将提供示例代码和优化建议,帮助开发者理解并规避此类性能瓶颈。 现象观察:p5.js…

    2025年12月20日
    000
  • JavaScript:根据对象特定字段拆分数组为重叠子集的高级技巧

    本文详细介绍了如何在JavaScript中将对象数组根据特定字段的存在性拆分为一系列重叠的子数组。核心方法通过迭代数组,利用条件判断处理首尾元素及中间分割点,确保分割字段同时包含在前后两个子集中,从而实现灵活的数据结构重组。 1. 问题定义与需求分析 在数据处理场景中,我们经常需要根据特定条件对数组…

    2025年12月20日
    000
  • JavaScript:基于特定字段存在性分割对象数组的教程

    本教程详细阐述如何在JavaScript中高效分割对象数组,其核心机制是依据对象内特定字段的存在与否来确定分割点。关键在于,触发分割的对象将被同时包含在当前子数组和下一个子数组中,并对数组的起始和结束边界进行特殊处理,以确保分割逻辑的准确性和灵活性。 需求分析与场景定义 在数据处理中,我们常遇到需要…

    2025年12月20日
    000
  • JavaScript:按特定字段分割对象数组并包含分割点

    本教程详细阐述了如何在JavaScript中根据对象内特定字段的存在来分割对象数组。文章通过迭代方法,解决了将分割标记对象同时包含在当前和下一个子数组中的复杂需求,并重点分析了边界条件处理,以确保生成准确无误的分割结果。 引言:对象数组的动态分割需求 在javascript开发中,我们经常需要处理复…

    2025年12月20日
    000
  • JavaScript模态框关闭按钮失效问题解析与解决方案

    本文深入探讨了在JavaScript中构建图片模态框时,关闭按钮无法正常工作的常见问题。核心症结在于事件冒泡和元素事件监听器的不当设置。通过将模态框的打开触发器与关闭按钮进行有效分离,并采用CSS类来管理模态框的显示状态,可以彻底解决事件冲突,提升代码的可维护性和用户体验。 构建交互式图片模态框的挑…

    2025年12月20日 好文分享
    000
  • 优化JavaScript图片弹窗关闭逻辑:避免事件冲突的实践指南

    本教程旨在解决JavaScript中图片弹窗关闭按钮失效的问题。当打开和关闭弹窗的事件源存在重叠时,可能导致事件冲突。核心解决方案是分离弹窗的打开和关闭触发器,并推荐使用CSS类来管理弹窗的显示状态,以提高代码的可维护性和用户体验,确保弹窗能够正常关闭。 理解问题根源:事件冒泡与冲突 在构建交互式网…

    2025年12月20日
    000
  • JavaScript模态框事件管理:避免关闭按钮冲突的策略与实践

    本教程旨在解决JavaScript中模态框关闭按钮与父级打开事件冲突的问题。通过分离模态框的打开与关闭事件触发器,并利用CSS类来管理模态框的显示状态,可以有效避免事件冒泡导致的意外行为,提升用户体验和代码可维护性。 在前端开发中,创建交互式模态框(弹出窗口)是常见的需求。当用户点击某个元素时,模态…

    2025年12月20日
    000
  • 根据输入框内容动态显示或隐藏Label标签的实用指南

    本文旨在提供一种根据HTML输入框(input)的内容,动态控制相关联Label标签显示与隐藏的解决方案。通过JavaScript和CSS的配合使用,当输入框为空时,隐藏对应的Label标签,反之则显示。本文将详细介绍如何通过CSS类切换以及直接修改display属性两种方法实现此功能,并提供相应的…

    2025年12月20日
    000
  • JavaScript模态框交互:解决弹出层关闭按钮无效问题

    本教程旨在解决JavaScript中创建图片弹出模态框时,关闭按钮点击无效的问题。核心原因在于打开和关闭事件触发器重叠或事件冒泡。解决方案是明确分离模态框的打开与关闭事件监听器,并推荐使用CSS类来管理模态框的显示状态,从而避免事件冲突,提升交互的稳定性和代码的可维护性。 在构建交互式前端界面时,常…

    2025年12月20日 好文分享
    000
  • 根据输入框内容动态显示/隐藏标签:JavaScript 实现指南

    本文旨在提供一种使用 JavaScript 根据输入框内容动态控制标签显示与隐藏的解决方案。通过监听输入框的值,并结合 CSS 样式控制,可以实现当输入框为空时隐藏标签,当输入框有内容时显示标签的效果,提升用户体验。本文将提供两种实现方法,分别使用 CSS 类切换和直接修改 display 属性。 …

    2025年12月20日
    000
  • 前端开发:基于表单输入值动态控制HTML元素显示

    本教程详细介绍了如何在前端开发中,根据用户在表单输入框中填写的值,动态控制另一个HTML元素的显示或隐藏。通过sessionStorage实现跨页面数据传递,并利用JavaScript结合CSS的两种方法(直接修改display属性或切换CSS类)来实现元素的条件渲染,确保页面内容的灵活性和用户体验…

    好文分享 2025年12月20日
    000
  • 根据输入框内容动态显示/隐藏标签:JavaScript 实现教程

    本文旨在提供一种使用 JavaScript 根据输入框内容动态控制 HTML 标签显示与隐藏的方法。通过监听输入框的值,并结合 CSS 样式或 JavaScript 的 style 属性,可以实现当输入框为空时隐藏标签,反之则显示标签的交互效果。本教程将提供两种实现方案,并附带代码示例和注意事项,帮…

    2025年12月20日
    000
  • 解决React中Debounced搜索在移动端过滤失效:大小写敏感性陷阱

    本教程探讨React应用中,使用防抖(Debounce)功能的搜索框在桌面端正常,但在移动端过滤失效的问题。核心原因在于移动设备输入自动首字母大写与过滤逻辑中数据源的小写转换处理不一致。文章将提供详细分析及解决方案,通过统一大小写处理来确保搜索功能在所有设备上稳定运行。 在react应用开发中,为提…

    2025年12月20日
    000

发表回复

登录后才能评论
关注微信