单数或数组模式Skill single-or-array-pattern

单数或数组模式是一种软件开发设计模式,用于创建能灵活处理单个项目或数组输入的函数。通过归一化输入到数组,实现统一的处理逻辑,适用于CRUD操作、批量处理API和工厂函数等场景。关键词:函数设计、CRUD、批量处理、归一化、API设计、软件开发模式。

架构设计 0 次安装 0 次浏览 更新于 3/20/2026

name: 单数或数组模式 description: 用于接受单个项目或数组的函数模式。在创建CRUD操作、批量处理API或工厂函数时使用,应灵活处理一个或多个输入。 metadata: author: epicenter version: ‘1.0’

单数或数组模式

接受单个项目和数组,在顶部进行归一化,统一处理。

快速参考

function create(itemOrItems: T | T[]): Promise<Result<void, E>> {
    const items = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
    // ... 实现在items数组上工作
}

结构

  1. 接受 T | T[] 作为参数类型
  2. 归一化 使用 Array.isArray() 在函数顶部
  3. 所有逻辑 针对数组工作 — 一个代码路径
function createServer(clientOrClients: Client | Client[], options?: Options) {
    const clients = Array.isArray(clientOrClients)
        ? clientOrClients
        : [clientOrClients];

    // 所有真实逻辑在这里,使用数组工作
    for (const client of clients) {
        // ...
    }
}

命名约定

参数 归一化变量
recordingOrRecordings recordings
clientOrClients clients
itemOrItems items
paramsOrParamsArray paramsArray

何时使用

适合:

  • CRUD操作(创建、更新、删除)
  • 批量处理API
  • 接受依赖项的工厂函数
  • 任何“对一个或多个执行此操作”的场景

跳过当:

  • 单数与批量有不同的语义
  • 返回类型显著不同
  • 数组版本需要不同的选项

代码库示例

服务器工厂 (packages/server/src/server.ts)

function createServer(
    clientOrClients: AnyWorkspaceClient | AnyWorkspaceClient[],
    options?: ServerOptions,
) {
    const clients = Array.isArray(clientOrClients)
        ? clientOrClients
        : [clientOrClients];

    // 所有服务器设置逻辑直接在这里
    const workspaces: Record<string, AnyWorkspaceClient> = {};
    for (const client of clients) {
        workspaces[client.id] = client;
    }
    // ...
}

数据库服务 (apps/whispering/src/lib/services/isomorphic/db/web.ts)

delete: async (recordingOrRecordings) => {
  const recordings = Array.isArray(recordingOrRecordings)
    ? recordingOrRecordings
    : [recordingOrRecordings];
  const ids = recordings.map((r) => r.id);
  return tryAsync({
    try: () => db.recordings.bulkDelete(ids),
    catch: (error) => DbServiceErr({ message: `删除错误: ${error}` }),
  });
},

查询突变 (apps/whispering/src/lib/query/isomorphic/db.ts)

delete: defineMutation({
  mutationFn: async (recordings: Recording | Recording[]) => {
    const recordingsArray = Array.isArray(recordings)
      ? recordings
      : [recordings];

    for (const recording of recordingsArray) {
      services.db.recordings.revokeAudioUrl(recording.id);
    }

    const { error } = await services.db.recordings.delete(recordingsArray);
    if (error) return Err(error);
    return Ok(undefined);
  },
}),

反模式

不要:为单数和数组分离函数

// 更难维护,用户必须记住两个API
function createRecording(recording: Recording): Promise<...>;
function createRecordings(recordings: Recording[]): Promise<...>;

不要:到处强制数组

// 对单个项目笨拙
createRecordings([recording]); // 丑陋

不要:在重载中重复逻辑

// 坏:逻辑重复
function create(item: T) {
    return db.insert(item); // 重复
}
function create(items: T[]) {
    return db.bulkInsert(items); // 不同代码路径
}

// 好:单一实现
function create(itemOrItems: T | T[]) {
    const items = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
    return db.bulkInsert(items); // 一个代码路径
}

参考