代码重构优化专家

330 浏览
32 试用
8 购买
Nov 24, 2025更新

本提示词可对提供的代码片段进行分析,判断是否适用指定设计模式,并提供详细实现思路、重构建议和修改关键点,帮助开发者优化代码质量和可维护性,适用于前端组件设计与重构。

下面给出基于“策略模式 Strategy”的重构方案与完整 TypeScript 代码。目标是将基于条件分支的校验逻辑解耦为可组合策略集,并通过统一上下文与策略注册表实现:可组合/可配置规则、链式执行与短路、异步规则(如远程占用校验)、错误消息国际化与聚合、以及在不同表单间的复用与可观测性。

实现思路概述

  • 抽象规则为策略:每个校验规则实现 RuleStrategy 接口,并只关心自身的校验与返回结果。新增/调整规则时只需在注册表注册,不修改核心调度逻辑。
  • 策略注册表:集中管理所有规则工厂,按名称获取并配置策略;支持组合策略(如 optional、group)。
  • 统一校验上下文:包含待校验值、国际化函数 t、locale、服务访问(异步远程校验)、可观测性事件发出等;便于规则内使用与测试注入。
  • 可组合与链式执行:
    • 字段管道:一个字段配置多个规则,按顺序执行。
    • 短路控制:字段层面可配置 shortCircuit;组合策略 group 内部可独立设置短路,实现“字段短路但某组规则聚合错误”的混合。
    • optional:实现“若为空则跳过后续规则”的语义。
  • 异步支持:规则 validate 返回 Promise;远程校验(如邮箱是否已占用)只在前置规则通过后再调用(依赖短路与顺序)。
  • 国际化与聚合:规则仅返回 code/params;统一用 t(code, params, locale) 翻译;字段可聚合多个错误;最终返回统一结构。
  • 复用与可测试性:策略纯函数化、上下文可注入;不同表单仅配置不同 schema;配合 registry 插件化扩展。
  • 可观测性:在规则开始/结束、字段结束时触发事件,便于日志/性能采样。

重构后的代码 说明:

  • 提供规则注册表、上下文、组合器、默认规则、示例 schema 与使用方式;
  • 演示异步邮箱占用校验;
  • 演示密码强度的组内聚合与字段短路混合;
  • 提供简单的国际化字典与 t 函数。
// ---------- 基础类型与上下文 ----------

type Values = Record<string, unknown>;

type Severity = 'error' | 'warn';
type RuleResult = {
  ok: boolean;
  code?: string;           // 国际化消息编码
  params?: Record<string, unknown>; // 国际化参数
  stop?: boolean;          // 指示短路(立即停止当前字段后续规则)
  severity?: Severity;
};

export type ValidationError<Field extends string = string> = {
  field: Field;
  code: string;
  message: string;
  severity: Severity;
};

export type ValidationEvent =
  | { type: 'rule:start'; field: string; rule: string; ts: number }
  | { type: 'rule:end'; field: string; rule: string; ts: number; durationMs: number; ok: boolean }
  | { type: 'field:end'; field: string; ts: number; durationMs: number; ok: boolean; errors: number };

export type Services = {
  // 示例:远程校验邮箱是否占用
  isEmailTaken?: (email: string, signal?: AbortSignal) => Promise<boolean>;
};

export type ValidationContext = {
  values: Values;
  locale: string;
  t: (code: string, params?: Record<string, unknown>, locale?: string) => string;
  services?: Services;
  emit?: (e: ValidationEvent) => void;           // 可观测性 hook
  abortSignal?: AbortSignal;
};

// ---------- 策略与注册表 ----------

export interface RuleStrategy {
  name: string;
  validate(value: unknown, ctx: ValidationContext, field: string): Promise<RuleResult>;
}

export type RuleFactory<Opts = unknown> =
  (opts: Opts, registry: RuleRegistry) => RuleStrategy;

export class RuleRegistry {
  private map = new Map<string, RuleFactory<any>>();

  register<Opts>(name: string, factory: RuleFactory<Opts>) {
    if (this.map.has(name)) throw new Error(`Rule already registered: ${name}`);
    this.map.set(name, factory);
  }

  create(name: string, opts: any): RuleStrategy {
    const f = this.map.get(name);
    if (!f) throw new Error(`Rule not found: ${name}`);
    return f(opts, this);
  }
}

// ---------- 组合描述与编译 ----------

export type RuleSpec = { use: string; opts?: any };
export type FieldPipelineConfig = {
  rules: RuleSpec[];
  shortCircuit?: boolean; // 字段层面默认短路
};
export type Schema<T extends Values> = {
  [K in keyof T]?: FieldPipelineConfig;
};

type CompiledPipeline = {
  field: string;
  strategies: RuleStrategy[];
  shortCircuit: boolean;
};

function compileSchema<T extends Values>(
  schema: Schema<T>,
  registry: RuleRegistry,
  shortCircuitDefault = true
): CompiledPipeline[] {
  const pipelines: CompiledPipeline[] = [];
  for (const field of Object.keys(schema)) {
    const conf = schema[field as keyof T]!;
    const strategies = conf.rules.map(spec => registry.create(spec.use, spec.opts));
    pipelines.push({
      field,
      strategies,
      shortCircuit: conf.shortCircuit ?? shortCircuitDefault,
    });
  }
  return pipelines;
}

// ---------- 国际化 ----------

const messages: Record<string, Record<string, string>> = {
  'zh-CN': {
    'required': '该字段为必填',
    'email.invalid': '邮箱格式不正确',
    'email.required': '邮箱必填',
    'email.taken': '该邮箱已被占用',
    'password.required': '密码必填',
    'password.minLength': '密码至少{min}位',
    'password.needUpper': '需包含大写字母',
    'password.needDigit': '需包含数字',
    'phone.invalid': '手机号格式错误',
    'agree.trueRequired': '需同意协议',
  },
  'en': {
    'required': 'This field is required',
    'email.invalid': 'Invalid email format',
    'email.required': 'Email is required',
    'email.taken': 'Email is already taken',
    'password.required': 'Password is required',
    'password.minLength': 'Password must be at least {min} characters',
    'password.needUpper': 'Must contain an uppercase letter',
    'password.needDigit': 'Must contain a digit',
    'phone.invalid': 'Invalid phone number',
    'agree.trueRequired': 'You must agree to the terms',
  },
};

function interpolate(template: string, params?: Record<string, unknown>): string {
  if (!params) return template;
  return template.replace(/\{(\w+)\}/g, (_, key) => String(params[key] ?? `{${key}}`));
}

export function t(code: string, params?: Record<string, unknown>, locale = 'zh-CN'): string {
  const dict = messages[locale] ?? messages['zh-CN'];
  const tpl = dict[code] ?? code;
  return interpolate(tpl, params);
}

// ---------- 默认规则(策略)实现 ----------

// required: 空值(undefined, null, '')判定
function isEmpty(v: unknown): boolean {
  return v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
}

const emailRegex = /^[^@]+@[^@]+\.[^@]+$/;
const phoneCnRegex = /^1\d{10}$/;

function asString(v: unknown): string | undefined {
  if (typeof v === 'string') return v;
  return v == null ? undefined : String(v);
}

function asBoolean(v: unknown): boolean | undefined {
  if (typeof v === 'boolean') return v;
  return v == null ? undefined : Boolean(v);
}

export function registerDefaultRules(registry: RuleRegistry) {
  // 1) required 通用
  registry.register('required', (_opts, _reg) => ({
    name: 'required',
    async validate(value) {
      const ok = !isEmpty(value);
      return ok ? { ok } : { ok: false, code: 'required' };
    },
  }));

  // 2) email 基本格式
  registry.register('email', (_opts, _reg) => ({
    name: 'email',
    async validate(value, _ctx, field) {
      const s = asString(value);
      const ok = !!s && emailRegex.test(s);
      return ok ? { ok } : { ok: false, code: field === 'email' ? 'email.invalid' : 'email.invalid' };
    },
  }));

  // 3) phone.cn(中国手机号)
  registry.register('phone.cn', (_opts, _reg) => ({
    name: 'phone.cn',
    async validate(value) {
      const s = asString(value);
      const ok = !!s && phoneCnRegex.test(s);
      return ok ? { ok } : { ok: false, code: 'phone.invalid' };
    },
  }));

  // 4) agree.true
  registry.register('agree.true', (_opts, _reg) => ({
    name: 'agree.true',
    async validate(value) {
      const b = asBoolean(value);
      const ok = b === true;
      return ok ? { ok } : { ok: false, code: 'agree.trueRequired' };
    },
  }));

  // 5) password 子规则
  registry.register<{ min: number }>('password.minLength', (opts, _reg) => ({
    name: 'password.minLength',
    async validate(value) {
      const s = asString(value) ?? '';
      const ok = s.length >= (opts?.min ?? 8);
      return ok ? { ok } : { ok: false, code: 'password.minLength', params: { min: opts?.min ?? 8 } };
    },
  }));

  registry.register('password.needUpper', (_opts, _reg) => ({
    name: 'password.needUpper',
    async validate(value) {
      const s = asString(value) ?? '';
      const ok = /[A-Z]/.test(s);
      return ok ? { ok } : { ok: false, code: 'password.needUpper' };
    },
  }));

  registry.register('password.needDigit', (_opts, _reg) => ({
    name: 'password.needDigit',
    async validate(value) {
      const s = asString(value) ?? '';
      const ok = /\d/.test(s);
      return ok ? { ok } : { ok: false, code: 'password.needDigit' };
    },
  }));

  // 6) optional 包装:值为空则直接通过
  registry.register<{ rule: RuleSpec }>('optional', (opts, registry) => {
    if (!opts?.rule) throw new Error('optional requires inner rule');
    const inner = registry.create(opts.rule.use, opts.rule.opts);
    return {
      name: `optional(${inner.name})`,
      async validate(value, ctx, field) {
        if (isEmpty(value)) return { ok: true, stop: true }; // 空值短路跳过后续
        return inner.validate(value, ctx, field);
      },
    };
  });

  // 7) group 组合:按配置聚合多个子规则的结果
  registry.register<{ rules: RuleSpec[]; shortCircuit?: boolean }>('group', (opts, registry) => {
    if (!opts?.rules?.length) throw new Error('group requires rules');
    const strategies = opts.rules.map(s => registry.create(s.use, s.opts));
    const short = opts.shortCircuit ?? false; // group 内默认聚合
    return {
      name: `group[${strategies.map(s => s.name).join(', ')}]`,
      async validate(value, ctx, field) {
        const errors: RuleResult[] = [];
        for (const s of strategies) {
          const start = performance.now();
          ctx.emit?.({ type: 'rule:start', field, rule: s.name, ts: start });
          const res = await s.validate(value, ctx, field);
          const end = performance.now();
          ctx.emit?.({
            type: 'rule:end',
            field,
            rule: s.name,
            ts: end,
            durationMs: end - start,
            ok: res.ok,
          });
          if (!res.ok) {
            errors.push(res);
            if (short || res.stop) break;
          }
        }
        // 聚合:若有错误,返回第一个错误;外层会继续收集并翻译所有错误
        if (errors.length === 0) return { ok: true };
        // 返回一个虚拟“失败但不携带具体 message”,由外层根据 errors 聚合
        // 为了传递全部错误,我们借助 params.errors 附带所有子错误的信息
        return { ok: false, code: '__group__', params: { errors } };
      },
    };
  });

  // 8) 远程邮箱占用校验
  registry.register('remote.emailNotTaken', (_opts, _reg) => ({
    name: 'remote.emailNotTaken',
    async validate(value, ctx) {
      const s = asString(value);
      if (!s) return { ok: false, code: 'email.required' };
      const fn = ctx.services?.isEmailTaken;
      if (!fn) {
        // 若服务未注入,保守认为可用,或返回 warn
        return { ok: true };
      }
      const taken = await fn(s, ctx.abortSignal);
      return taken ? { ok: false, code: 'email.taken' } : { ok: true };
    },
  }));
}

// ---------- 核心验证器 ----------

export type ValidatorOptions = {
  defaultLocale?: string;
  shortCircuitDefault?: boolean;
  services?: Services;
  emit?: (e: ValidationEvent) => void;
  abortSignal?: AbortSignal;
  t?: ValidationContext['t'];
};

export type ValidationResult<Field extends string = string> = {
  ok: boolean;
  errors: ValidationError<Field>[];
};

export function createValidator<T extends Values>(
  schema: Schema<T>,
  registry: RuleRegistry,
  options: ValidatorOptions = {}
) {
  const pipelines = compileSchema(schema, registry, options.shortCircuitDefault ?? true);

  return async function validate(values: T, locale?: string): Promise<ValidationResult<Extract<keyof T, string>>> {
    const loc = locale ?? options.defaultLocale ?? 'zh-CN';
    const ctx: ValidationContext = {
      values,
      locale: loc,
      t: options.t ?? t,
      services: options.services,
      emit: options.emit,
      abortSignal: options.abortSignal,
    };

    const errors: ValidationError[] = [];

    for (const p of pipelines) {
      const fieldStart = performance.now();
      const val = values[p.field as keyof T];

      let fieldOk = true;
      for (const s of p.strategies) {
        const ruleStart = performance.now();
        ctx.emit?.({ type: 'rule:start', field: p.field, rule: s.name, ts: ruleStart });

        const res = await s.validate(val, ctx, p.field);

        const ruleEnd = performance.now();
        ctx.emit?.({
          type: 'rule:end',
          field: p.field,
          rule: s.name,
          ts: ruleEnd,
          durationMs: ruleEnd - ruleStart,
          ok: res.ok,
        });

        if (!res.ok) {
          fieldOk = false;
          if (res.code === '__group__' && res.params?.errors && Array.isArray(res.params.errors)) {
            // 展开 group 子错误
            for (const sub of res.params.errors as RuleResult[]) {
              if (!sub.ok && sub.code) {
                errors.push({
                  field: p.field,
                  code: sub.code,
                  message: ctx.t(sub.code, sub.params, loc),
                  severity: sub.severity ?? 'error',
                });
              }
            }
          } else if (res.code) {
            errors.push({
              field: p.field,
              code: res.code,
              message: ctx.t(res.code, res.params, loc),
              severity: res.severity ?? 'error',
            });
          }
          if (p.shortCircuit || res.stop) {
            break;
          }
        }
      }

      const fieldEnd = performance.now();
      ctx.emit?.({
        type: 'field:end',
        field: p.field,
        ts: fieldEnd,
        durationMs: fieldEnd - fieldStart,
        ok: fieldOk,
        errors: errors.filter(e => e.field === p.field).length,
      });
    }

    return { ok: errors.length === 0, errors };
  };
}

// ---------- 用于原始示例 Form 的重构 ----------

type Form = { email?: string; password?: string; phone?: string; agree?: boolean };

// 构建注册表并注册默认规则
const registry = new RuleRegistry();
registerDefaultRules(registry);

// 针对 Form 的 schema:
// - email: 必填 + 格式 + 远程占用(短路保证远程只在前置规则通过时调用)
// - password: 组内聚合子规则(不短路),字段层面仍短路(只有一个 group)
// - phone: optional 包裹手机号规则(为空pass,否则校验格式)
// - agree: 必须为 true
const formSchema: Schema<Form> = {
  email: {
    shortCircuit: true,
    rules: [
      { use: 'required' },
      { use: 'email' },
      { use: 'remote.emailNotTaken' },
    ],
  },
  password: {
    // 字段短路:只有一个 group;group 内不短路,聚合所有错误
    shortCircuit: true,
    rules: [
      {
        use: 'group',
        opts: {
          shortCircuit: false,
          rules: [
            { use: 'required' },
            { use: 'password.minLength', opts: { min: 8 } },
            { use: 'password.needUpper' },
            { use: 'password.needDigit' },
          ],
        },
      },
    ],
  },
  phone: {
    shortCircuit: true,
    rules: [
      { use: 'optional', opts: { rule: { use: 'phone.cn' } } },
    ],
  },
  agree: {
    shortCircuit: true,
    rules: [
      { use: 'agree.true' },
    ],
  },
};

// 创建验证器(可注入服务与可观测性)
export const validateForm = (() => {
  // 注入示例远程服务:真实环境中改为 API 调用
  const services: Services = {
    isEmailTaken: async (email: string) => {
      // demo:模拟 remote,占用 "taken@example.com"
      await new Promise(r => setTimeout(r, 50));
      return email.toLowerCase() === 'taken@example.com';
    },
  };

  // 可观测性示例:输出到控制台(生产中可换为日志/埋点)
  const emit = (e: ValidationEvent) => {
    // 简化:只在开发时打印
    // console.debug('[validation]', e);
  };

  const validator = createValidator<Form>(formSchema, registry, {
    defaultLocale: 'zh-CN',
    services,
    emit,
  });

  // 兼容原导出签名:返回 ok + errors 的字符串数组(与原函数一致)
  return async (f: Form) => {
    const res = await validator(f, 'zh-CN');
    return {
      ok: res.ok,
      errors: res.errors.map(e => e.message), // 保留原有返回结构
    };
  };
})();

// ---------- 使用示例(与原函数一致) ----------
// (async () => {
//   const r = await validateForm({ email: 'taken@example.com', password: 'abc', phone: '123', agree: false });
//   console.log(r);
// })();

设计亮点与扩展建议

  • 新增规则无需修改核心:添加新策略只需 registry.register('my.rule', factory);schema 引用即可。
  • 规则复用:同一规则可在不同表单/字段重用;可通过 opts 参数调整行为(如密码最小长度)。
  • 可组合策略:
    • optional 实现“若为空则跳过”的语义;
    • group 实现内部规则聚合错误并单独短路控制;
  • 异步短路:常见远程规则(如占用校验)放在同步规则之后,字段短路保证仅在格式正确后才发起远程请求,减少不必要请求。
  • 国际化:规则返回 code/params,最终统一翻译;方便扩展更多语言与文案。
  • 可观测性:事件包含 rule.start/end 与 field.end,可用于性能诊断与日志;也便于在单元测试中断言执行顺序与次数。
  • 单元测试友好:
    • 规则函数纯净(依赖通过 ctx 注入);
    • 可在测试中注入 mock services/t/emit;
    • 对每个规则与组合策略单独编写测试用例。

如需将输出结构改为更丰富(携带 field、code),可直接返回 ValidationResult 而非字符串数组,前端按需展示即可。

下面给出重构思路与实现,采用抽象工厂(Abstract Factory)来统一产出同族 UI 组件(Button、Card),把主题分支隔离到具体工厂中。新增主题只需实现新的工厂类,无需改动现有组件逻辑。样式以纯对象返回,支持 SSR;按需引入不同主题工厂,利于树摇优化;同时统一设计令牌(Design Tokens)与语义色(Semantic Colors),提升一致性与扩展性。

实现思路

  • 抽象工厂接口 UIFactory:定义 createButton、createCard 的产出契约;并暴露 tokens 与 semanticColors,保证组件使用统一令牌与语义色。
  • 具体工厂 LightFactory、DarkFactory:各自基于同一套 Design Tokens 与不同的语义色(Semantic Colors)生成样式对象(不碰 DOM)。
  • 样式模型 UIElement:是 SSR 友好的纯数据结构(tag、attrs、style、children)。提供可选的挂载器 mountToDOM 与 toHTML,分别用于浏览器与 SSR。
  • 组件逻辑无条件分支:Button/Card 的逻辑只依赖工厂传入的语义色与令牌,不包含 theme 判断。新增主题新增一个工厂即可。
  • 按需引入与树摇:不同主题实现在不同模块中,使用者只 import 需要的工厂实现,未引入的主题会被摇掉。
  • 设计令牌与语义色统一:
    • DesignTokens:空间(space)、圆角(radius)、阴影(shadow)等。
    • SemanticColors:surface、surfaceElevated、textPrimary、borderDefault、shadowLow/High 等。不同主题的具体值不同,但语义相同。

重构后的代码(按文件模块划分示例) /* ui/types.ts */ export type Style = Readonly<Record<string, string | number>>;

export type UIChild = string | UIElement;

export interface UIElement { tag: string; attrs?: Readonly<Record<string, string>>; style?: Style; children?: ReadonlyArray; }

export interface DesignTokens { space: { xs: number; sm: number; md: number; lg: number; }; radius: { sm: number; md: number; }; shadow: { low: string; high: string; }; }

export interface SemanticColors { surface: string; // 基础面 surfaceElevated: string; // 提升面(卡片) textPrimary: string; borderDefault: string; shadowLow: string; shadowHigh: string; }

export interface UIFactory { readonly tokens: DesignTokens; readonly semantic: SemanticColors; createButton(label: string): UIElement; createCard(title: string, content: string): UIElement; }

/* ui/renderers.ts - 可选:用于浏览器或 SSR 挂载 */ export function mountToDOM(el: UIElement): HTMLElement { const node = document.createElement(el.tag); if (el.attrs) { Object.entries(el.attrs).forEach(([k, v]) => node.setAttribute(k, v)); } if (el.style) { Object.entries(el.style).forEach(([k, v]) => { const value = typeof v === 'number' ? ${v}px : v; // @ts-ignore node.style[k] = value; }); } if (el.children) { el.children.forEach(child => { if (typeof child === 'string') { node.appendChild(document.createTextNode(child)); } else { node.appendChild(mountToDOM(child)); } }); } return node; }

export function toHTML(el: UIElement): string { const attrs = el.attrs ? ' ' + Object.entries(el.attrs) .map(([k, v]) => ${k}="${escapeHtml(v)}") .join(' ') : ''; const style = el.style ? ' style="' + Object.entries(el.style) .map(([k, v]) => ${k}:${typeof v === 'number' ? ${v}px : v}) .join(';') + '"' : ''; const open = <${el.tag}${attrs}${style}>; const inner = (el.children || []) .map(child => (typeof child === 'string' ? escapeHtml(child) : toHTML(child))) .join(''); const close = </${el.tag}>; return open + inner + close; }

function escapeHtml(s: string): string { return s .replace(/&/g, '&') .replace(/"/g, '"') .replace(/</g, '<') .replace(/>/g, '>'); }

/* ui/baseTokens.ts - 公共令牌 */ export const baseTokens: DesignTokens = { space: { xs: 4, sm: 8, md: 16, lg: 24 }, radius: { sm: 4, md: 8 }, // 阴影字符串由不同语义色决定,在工厂里组合;此处给出形态模板。 shadow: { low: '0 1px 3px', // 颜色在语义色里定义 high: '0 1px 3px', // 示例保持一致,颜色不同 }, } as const;

/* themes/light.ts - 具体工厂:浅色主题 */ import { UIFactory, UIElement, SemanticColors } from '../ui/types'; import { baseTokens } from '../ui/baseTokens';

export class LightFactory implements UIFactory { readonly tokens = baseTokens; readonly semantic: SemanticColors = { surface: '#ffffff', surfaceElevated: '#ffffff', textPrimary: '#222222', borderDefault: '#dddddd', shadowLow: 'rgba(0,0,0,.1)', shadowHigh: 'rgba(0,0,0,.2)', };

createButton(label: string): UIElement { const { semantic, tokens } = this; return { tag: 'button', style: { background: semantic.surface, color: semantic.textPrimary, border: 1px solid ${semantic.borderDefault}, padding: ${tokens.space.sm}px ${tokens.space.md}px, borderRadius: tokens.radius.sm, }, children: [label], }; }

createCard(title: string, content: string): UIElement { const { semantic, tokens } = this; return { tag: 'div', attrs: { 'data-title': title, 'data-content': content, }, style: { background: semantic.surfaceElevated, // 合成阴影时组合令牌形态与语义色 boxShadow: ${tokens.shadow.low} ${semantic.shadowLow}, padding: tokens.space.md, borderRadius: tokens.radius.md, }, children: [], }; } }

/* themes/dark.ts - 具体工厂:深色主题 */ import { UIFactory, UIElement, SemanticColors } from '../ui/types'; import { baseTokens } from '../ui/baseTokens';

export class DarkFactory implements UIFactory { readonly tokens = baseTokens; readonly semantic: SemanticColors = { surface: '#222222', surfaceElevated: '#1b1b1b', textPrimary: '#ffffff', borderDefault: '#444444', shadowLow: 'rgba(0,0,0,.6)', shadowHigh: 'rgba(0,0,0,.7)', };

createButton(label: string): UIElement { const { semantic, tokens } = this; return { tag: 'button', style: { background: semantic.surface, color: semantic.textPrimary, border: 1px solid ${semantic.borderDefault}, padding: ${tokens.space.sm}px ${tokens.space.md}px, borderRadius: tokens.radius.sm, }, children: [label], }; }

createCard(title: string, content: string): UIElement { const { semantic, tokens } = this; return { tag: 'div', attrs: { 'data-title': title, 'data-content': content, }, style: { background: semantic.surfaceElevated, boxShadow: ${tokens.shadow.low} ${semantic.shadowLow}, padding: tokens.space.md, borderRadius: tokens.radius.md, }, children: [], }; } }

/* components/index.ts - 组件层(不关心主题分支) */ import { UIFactory, UIElement } from '../ui/types';

export function renderButton(factory: UIFactory, label: string): UIElement { // 组件仅依赖工厂,不做 theme 判断 return factory.createButton(label); }

export function renderCard(factory: UIFactory, title: string, content: string): UIElement { return factory.createCard(title, content); }

/* index.ts - 使用示例(按需引入,利于树摇) */ // 选择主题工厂:只引入需要的主题 import { LightFactory } from './themes/light'; // 或:import { DarkFactory } from './themes/dark';

import { renderButton, renderCard } from './components'; import { mountToDOM, toHTML } from './ui/renderers';

const factory = new LightFactory();

const btnModel = renderButton(factory, 'Click Me'); const cardModel = renderCard(factory, 'Title', 'Content');

// 浏览器挂载 document.body.appendChild(mountToDOM(btnModel)); document.body.appendChild(mountToDOM(cardModel));

// SSR 渲染(示例) const html = toHTML(cardModel); // 在 SSR 环境返回 html 字符串即可

关键改动说明

  • 抽象工厂替代主题分支:
    • 原先 renderButton/renderCard 内部通过 if(theme) 分支设置样式;现在组件只调用传入 factory 的 createXXX 方法,不含任何主题判断。
  • 样式对象化,支持 SSR:
    • 组件与工厂返回 UIElement(纯对象),不直接操作 DOM。提供独立挂载器 mountToDOM 与 SSR 转换器 toHTML,分别用于客户端和服务端。
  • 统一令牌与语义色:
    • baseTokens 统一空间、圆角、阴影形态;各主题通过 semanticColors 赋予具体颜色与阴影透明度。
  • 扩展新主题:
    • 新增主题只需新增一个工厂,如 HighContrastFactory,实现 UIFactory 接口,填入对应的语义色即可,无需动组件代码。
  • 按需引入与树摇:
    • LightFactory、DarkFactory 分模块导出;应用只 import 需要的主题,未引入的主题实现不会被打包。

迁移建议

  • 将原始 API 从 renderButton(theme: 'light' | 'dark', ...) 调整为 renderButton(factory: UIFactory, ...)。
  • 如需保留旧签名,可提供轻量包装层在应用入口将 'light' | 'dark' 解析为对应工厂实例,但这会降低树摇效果,推荐直接按需 import 工厂。

这样重构后,主题扩展成本显著降低,代码更具一致性与可维护性,同时满足 SSR 与按需引入的工程化要求。

实现思路(Observer 模式落地要点)

  • 将“语言切换”抽象为可复用的主题 LocaleSubject。它维护当前语言状态(state)与订阅者集合(observers),提供 next(lang) 触发语言切换。
  • 订阅方式支持两类:
    1. 对象 + 方法名(弱引用 WeakRef)。优点:不持有组件强引用,懒加载后订阅时可立即获取最新状态;组件释放后,无需显式退订可自动清理(FinalizationRegistry)。
    2. 纯函数回调(强引用)。需要显式退订;适合非组件场景或者测试探针。
  • 广播去重与合并:
    • 相等值去重:相同语言不广播(默认可配置)。
    • 调用合并:同一事件循环内多次 next 仅最后一次生效(microtask flush 合并),避免抖动。
  • 防重复订阅与内存管理:
    • 同一接收者 + 方法名去重(弱索引:WeakMap),重复订阅直接复用或返回已订阅 token。
    • 纯函数回调按函数引用去重。
    • FinalizationRegistry 自动剔除已 GC 的对象订阅。
  • 懒加载组件立即“补发”最新状态:
    • options.immediate = true,订阅时立刻收到当前语言,无需等待下一次切换。
  • 可观测性与测试钩子:
    • 测试查询:getState、getSubscriberSnapshot、getMetrics、flush。
    • 事件探针:testing.tap(fn) 可观察每次广播明细;也可选择性地发送 window.dispatchEvent(new CustomEvent('locale:changed', ...)) 或公开 window.LOCALE_BUS 以便端到端捕获。
  • 失败隔离:
    • 某个观察者抛错不会影响其他观察者;错误会上报到 metrics 与 onError 钩子,便于定位。
  • API 简要:
    • subscribe(target, methodName, options) | subscribe(fn, options)
    • unsubscribe(token)
    • next(lang, { force })
    • testing: { getState, flush, getSubscriberSnapshot, getMetrics, tap, unTap }

重构后的代码(复杂度较高,含注释与测试钩子) /**

  • LocaleSubject - 语言切换的可复用主题(Observer 模式)
  • 特性:
    • 对象订阅弱引用 + FinalizationRegistry 自动清理
    • 函数订阅强引用(显式退订)
    • 去重广播、同轮次合并(microtask)
    • 防重复订阅(对象+方法名/函数去重)
    • 懒加载订阅立即补发(immediate)
    • 测试与可观测性钩子 */

/** 简单的相等比较,可按需替换 */ function defaultEquals(a, b) { return a === b; }

const hasFinalizationRegistry = typeof FinalizationRegistry === 'function'; const hasWeakRef = typeof WeakRef === 'function';

let __subId = 0; function nextId() { __subId += 1; return __subId; }

/**

  • @typedef {Object} SubscribeOptions
  • @property {boolean} [immediate=false] - 订阅后是否立即收到当前语言
  • @property {boolean} [once=false] - 是否只接收一次广播
  • @property {string} [tag] - 订阅者调试标签
  • @property {string} [dedupeKey] - 自定义去重键,默认用 methodName 或 fn 引用 */

/**

  • @typedef {Object} NextOptions
  • @property {boolean} [force=false] - 忽略相等判断强制广播 */

/**

  • 每个订阅者记录
    • objectRef + methodName:弱引用订阅
    • fn:函数订阅 */ class SubscriberRecord { constructor(kind, payload, options) { this.id = nextId(); this.kind = kind; // 'object' | 'function' this.tag = options?.tag || undefined; this.once = !!options?.once; this.immediate = !!options?.immediate; this.dedupeKey = options?.dedupeKey; this.active = true; if (kind === 'object') { this.objectRef = payload.objectRef; // WeakRef | object (fallback) this.methodName = payload.methodName; // string this.finalizerToken = payload.finalizerToken; // object } else { this.fn = payload.fn; // function } } }

      export class LocaleSubject { /**

      • @param {string} initial - 初始语言
      • @param {Object} [opts]
      • @param {(a:any,b:any)=>boolean} [opts.equals=defaultEquals] - 自定义相等判断
      • @param {boolean} [opts.dedupeSameValue=true] - 相同值不广播
      • @param {boolean} [opts.coalesceMicrotask=true] - 同事件循环内合并广播
      • @param {string} [opts.name='LocaleSubject'] - 调试名称
      • @param {(err:Error, ctx:any)=>void} [opts.onError] - 观察者执行错误回调
      • @param {(evt:any)=>void} [opts.onEvent] - 广播事件回调(可用于可观测性)
      • @param {boolean} [opts.emitDomEvent=false] - 是否派发 DOM CustomEvent */ constructor(initial, opts = {}) { this.name = opts.name || 'LocaleSubject'; this.equals = opts.equals || defaultEquals; this.dedupeSameValue = opts.dedupeSameValue !== false; this.coalesceMicrotask = opts.coalesceMicrotask !== false; this.onError = typeof opts.onError === 'function' ? opts.onError : null; this.onEvent = typeof opts.onEvent === 'function' ? opts.onEvent : null; this.emitDomEvent = !!opts.emitDomEvent;
      this._state = initial;
      this._pending = { has: false, value: undefined };
      this._scheduled = false;
      
      this.subscribers = new Map(); // id -> SubscriberRecord
      // 对象订阅去重:WeakMap<object, Map<dedupeKey, id>>
      this.objectIndex = new WeakMap();
      // 函数订阅去重:Map<fn, id>
      this.fnIndex = new Map();
      
      // Finalization: 自动清理弱引用订阅
      if (hasFinalizationRegistry) {
        this.finalizer = new FinalizationRegistry((token) => {
          // token -> id 用来移除 subscriber
          const id = token && token.__id;
          if (id && this.subscribers.has(id)) {
            this.subscribers.delete(id);
            // 无法从 objectIndex 反查对象,但弱引用已回收,冗余记录影响不大
            this._metrics.subscriberAutoCollected += 1;
          }
        });
      } else {
        this.finalizer = null;
      }
      
      // metrics for testing/observability
      this._metrics = {
        name: this.name,
        totalBroadcasts: 0,
        totalSkippedEqualValue: 0,
        lastBroadcastAt: 0,
        lastBroadcastId: 0,
        subscriberCount: 0,
        subscriberAutoCollected: 0,
        lastErrors: [],
      };
      
      // testing taps
      this._taps = new Set();
      
      // 暴露全局 hook(可选)
      // 可在 E2E 中通过 window.__LOCALE_BUS__ 访问
      if (typeof window !== 'undefined') {
        if (!window.__LOCALE_BUS__) window.__LOCALE_BUS__ = {};
        window.__LOCALE_BUS__[this.name] = this;
      }
      

      }

      // 当前状态 getState() { return this._state; }

      // 内部:调度 microtask 合并广播 _ensureScheduled() { if (this._scheduled && this.coalesceMicrotask) return; this._scheduled = true; const schedule = typeof queueMicrotask === 'function' ? queueMicrotask : (fn) => Promise.resolve().then(fn); schedule(() => { this._scheduled = false; this._flush(); }); }

      // 外部:设置下一语言 next(lang, options = {}) { const force = !!options.force; if (this.dedupeSameValue && !force && this.equals(this._state, lang)) { this._metrics.totalSkippedEqualValue += 1; return; } // 合并策略:仅保留最后一次 this._pending.has = true; this._pending.value = lang; this._ensureScheduled(); }

      // 订阅(对象弱引用版本) // subject.subscribe(object, 'setLang', { immediate: true, tag: 'header' }) subscribe(targetOrFn, maybeMethodNameOrOptions, maybeOptions) { if (typeof targetOrFn === 'function') { // 函数订阅 const fn = targetOrFn; const options = (maybeMethodNameOrOptions && typeof maybeMethodNameOrOptions === 'object') ? maybeMethodNameOrOptions : (maybeOptions || {}); return this._subscribeFunction(fn, options); }

      const target = targetOrFn;
      const methodName = typeof maybeMethodNameOrOptions === 'string'
        ? maybeMethodNameOrOptions
        : null;
      const options = (typeof maybeMethodNameOrOptions === 'object')
        ? maybeMethodNameOrOptions
        : (maybeOptions || {});
      
      if (!target || typeof methodName !== 'string') {
        throw new Error('subscribe(object, methodName, options) 需要 object + methodName');
      }
      
      return this._subscribeObject(target, methodName, options);
      

      }

      _subscribeFunction(fn, options) { if (typeof fn !== 'function') throw new Error('subscribe(fn) fn 必须是函数');

      // 防重复订阅:同一函数引用只保留一个
      if (this.fnIndex.has(fn)) {
        const id = this.fnIndex.get(fn);
        const rec = this.subscribers.get(id);
        if (rec && rec.active) {
          if (options?.immediate) {
            try {
              fn(this._state);
            } catch (err) {
              this._recordError(err, { phase: 'immediate', subscriberId: rec.id, tag: rec.tag });
            }
          }
          return { unsubscribe: () => this._unsubscribeById(id), token: id };
        }
      }
      
      const rec = new SubscriberRecord('function', { fn }, options);
      this.subscribers.set(rec.id, rec);
      this.fnIndex.set(fn, rec.id);
      this._syncMetrics();
      
      if (options?.immediate) {
        try {
          fn(this._state);
        } catch (err) {
          this._recordError(err, { phase: 'immediate', subscriberId: rec.id, tag: rec.tag });
        }
      }
      
      return { unsubscribe: () => this._unsubscribeById(rec.id), token: rec.id };
      

      }

      _subscribeObject(target, methodName, options) { const dedupeKey = options?.dedupeKey || methodName;

      // 防重复订阅:同一对象 + dedupeKey
      let perObj = this.objectIndex.get(target);
      if (perObj && perObj.has(dedupeKey)) {
        const id = perObj.get(dedupeKey);
        const rec = this.subscribers.get(id);
        if (rec && rec.active) {
          if (options?.immediate) {
            const receiver = target;
            if (typeof receiver[methodName] === 'function') {
              try {
                receiver[methodName](this._state);
              } catch (err) {
                this._recordError(err, { phase: 'immediate', subscriberId: rec.id, tag: rec.tag });
              }
            }
          }
          return { unsubscribe: () => this._unsubscribeById(id), token: id };
        }
      }
      
      // 构造弱引用
      const objectRef = hasWeakRef ? new WeakRef(target) : target;
      const finalizerToken = { __id: null };
      
      const rec = new SubscriberRecord('object', { objectRef, methodName, finalizerToken }, options);
      this.subscribers.set(rec.id, rec);
      finalizerToken.__id = rec.id;
      
      if (!perObj) {
        perObj = new Map();
        this.objectIndex.set(target, perObj);
      }
      perObj.set(dedupeKey, rec.id);
      
      // 注册最终化清理
      if (this.finalizer && hasWeakRef) {
        this.finalizer.register(target, finalizerToken, finalizerToken);
      }
      
      this._syncMetrics();
      
      if (options?.immediate) {
        const receiver = target;
        if (typeof receiver[methodName] === 'function') {
          try {
            receiver[methodName](this._state);
          } catch (err) {
            this._recordError(err, { phase: 'immediate', subscriberId: rec.id, tag: rec.tag });
          }
        }
      }
      
      return { unsubscribe: () => this._unsubscribeById(rec.id), token: rec.id };
      

      }

      unsubscribeById(id) { const rec = this.subscribers.get(id); if (!rec) return; rec.active = false; this.subscribers.delete(id); if (rec.kind === 'function') { this.fnIndex.forEach((val, key) => { if (val === id) this.fnIndex.delete(key); }); } else if (rec.kind === 'object') { // 无法反查原始对象(弱引用),但 objectIndex 中的记录不影响 GC,仅是少量冗余 if (this.finalizer && rec.finalizerToken) { try { this.finalizer.unregister(rec.finalizerToken); } catch () {} } } this._syncMetrics(); }

      _flush() { // 没有 pending 则不广播 if (!this._pending.has) return;

      const nextValue = this._pending.value;
      this._pending.has = false;
      this._pending.value = undefined;
      
      if (this.dedupeSameValue && this.equals(this._state, nextValue)) {
        this._metrics.totalSkippedEqualValue += 1;
        return;
      }
      
      // 广播
      const prev = this._state;
      this._state = nextValue;
      const notifyId = ++this._metrics.lastBroadcastId;
      const now = Date.now();
      this._metrics.totalBroadcasts += 1;
      this._metrics.lastBroadcastAt = now;
      
      const errors = [];
      
      // 快照当前订阅者,防止回调过程中增删订阅影响本轮
      const list = Array.from(this.subscribers.values());
      
      for (const rec of list) {
        if (!rec.active) continue;
      
        try {
          if (rec.kind === 'function') {
            rec.fn(nextValue);
          } else {
            // 对象弱引用
            const obj = hasWeakRef ? rec.objectRef.deref() : rec.objectRef;
            if (!obj) {
              // 已被 GC
              this.subscribers.delete(rec.id);
              continue;
            }
            const method = obj[rec.methodName];
            if (typeof method === 'function') {
              method.call(obj, nextValue);
            }
          }
        } catch (err) {
          errors.push({ err, subscriberId: rec.id, tag: rec.tag });
          this._recordError(err, { phase: 'broadcast', subscriberId: rec.id, tag: rec.tag });
        }
      
        if (rec.once) {
          this._unsubscribeById(rec.id);
        }
      }
      
      // 可观测性事件
      const evt = {
        type: 'locale:changed',
        name: this.name,
        prev,
        next: nextValue,
        at: now,
        notifyId,
        subscriberCount: this.subscribers.size,
        errors,
      };
      if (this.onEvent) {
        try { this.onEvent(evt); } catch (_) {}
      }
      for (const tap of this._taps) {
        try { tap(evt); } catch (_) {}
      }
      if (this.emitDomEvent && typeof window !== 'undefined' && typeof window.dispatchEvent === 'function') {
        try {
          window.dispatchEvent(new CustomEvent('locale:changed', { detail: evt }));
        } catch (_) {}
      }
      

      }

      _recordError(err, ctx) { this.metrics.lastErrors.push({ at: Date.now(), err, ctx }); if (this.onError) { try { this.onError(err, ctx); } catch () {} } }

      _syncMetrics() { this._metrics.subscriberCount = this.subscribers.size; }

      // 测试与可观测性钩子 get testing() { return { getState: () => this._state, flush: () => this._flush(), getSubscriberSnapshot: () => Array.from(this.subscribers.values()).map(s => ({ id: s.id, kind: s.kind, tag: s.tag, once: s.once, active: s.active, })), getMetrics: () => ({ ...this._metrics }), tap: (fn) => { this._taps.add(fn); return () => this._taps.delete(fn); }, unTap: (fn) => { this._taps.delete(fn); }, }; } }

      /** --------- 使用方式与对原代码的重构 --------- **/

      // 1) 创建全局可复用的 localeBus export const localeBus = new LocaleSubject('en-US', { name: 'LocaleBus', emitDomEvent: false, // 若前端 E2E 想用 DOM 事件,改为 true onError: (err, ctx) => { // 可接入监控平台 console.error('[LocaleBus] observer error:', err, ctx); }, onEvent: (evt) => { // 可接入可观测性系统 / 日志 // console.log('[LocaleBus] event:', evt); }, });

      // 2) 组件侧:按需订阅与退订(弱引用,避免强耦合) const components = { header: { setLang: (l) => console.log('header ->', l) }, sidebar: { setLang: (l) => console.log('sidebar ->', l) }, footer: { setLang: (l) => console.log('footer ->', l) }, forms: [{ setLang: (l) => console.log('form1 ->', l) }], };

      // 初次装配:谁需要语言就订阅,新增/懒加载不会改 controller export function wireLocaleForInitialComponents() { // immediate: 订阅时立刻拿到当前语言 localeBus.subscribe(components.header, 'setLang', { immediate: true, tag: 'header' }); localeBus.subscribe(components.sidebar, 'setLang', { immediate: true, tag: 'sidebar' }); localeBus.subscribe(components.footer, 'setLang', { immediate: true, tag: 'footer' }); components.forms.forEach((f, i) => { localeBus.subscribe(f, 'setLang', { immediate: true, tag: form-${i + 1} }); }); }

      // 懒加载组件:在组件 mount 时调用 export function onLazyComponentMounted(comp, tag) { // 去重:同一个 comp + 'setLang' 重复调用不会重复订阅 const sub = localeBus.subscribe(comp, 'setLang', { immediate: true, tag }); // 若组件有卸载周期:return sub.unsubscribe 在卸载时调用 return sub; }

      // 3) 替换原 applyLocale:控制器不再关心具体组件 export function applyLocale(lang) { localeBus.next(lang); // 如需强制广播(即使相等),使用:localeBus.next(lang, { force: true }) }

      // 4) 用户操作:仅决定下一语言,交给主题广播 export function onUserToggleLocale() { const next = Math.random() > 0.5 ? 'zh-CN' : 'en-US'; applyLocale(next); }

      /** --------- 端到端测试与可观测示例 --------- **/

      // E2E: 订阅探针收集事件 export function attachE2EProbe() { const events = []; const detach = localeBus.testing.tap((evt) => events.push(evt)); return { events, detach, metrics: () => localeBus.testing.getMetrics(), state: () => localeBus.testing.getState(), flush: () => localeBus.testing.flush(), }; }

      // 示例:初始化并触发一次 wireLocaleForInitialComponents(); // applyLocale('zh-CN'); // 正常广播 // applyLocale('zh-CN'); // 因去重跳过 // applyLocale('en-US'); // 广播

      需要修改的关键点

      • 删除原 applyLocale 中对每个组件的显式调用。由 localeBus.next(lang) 替代,控制器与组件解耦。
      • 各组件在各自生命周期里调用 localeBus.subscribe(this, 'setLang', { immediate: true, tag: 'xxx' }),懒加载时也能立即获得最新语言。
      • 复用 LocaleSubject:适用于任何“全局状态变更广播”场景(例如主题切换、FeatureFlag、在线/离线状态),不局限于语言。

      加分说明

      • WeakRef 与 FinalizationRegistry 在现代浏览器与 Node 的新版本可用;若运行环境较老,仍能工作,只是无法自动清理弱引用(需要手动 unsubscribe)。
      • 合并策略采用 microtask(queueMicrotask/Promise.then),同步多次 next 只广播一次最新值,减少渲染抖动。
      • 去重策略默认基于严格相等,可通过构造参数 equals 自定义(例如基于深比较或标准化字符串比较)。

      示例详情

      解决的问题

      帮助软件开发者结合设计模式优化现有代码,提高代码的可读性、可维护性与复用性,以及提升系统整体质量。

      适用用户

      初级开发者

      借助提示词学习设计模式的实际应用方法,并提升代码质量与编程能力。

      资深程序员

      优化现有代码结构,解决复杂问题,快速打磨高质量代码。

      技术团队负责人

      为团队制定一致的代码重构规范,提升整体代码维护性与减少技术债务。

      特征总结

      智能分析代码片段,快速识别潜在问题并提供优化建议。
      支持多种主流编程语言的代码审查,为开发者提供高适配性解决方案。
      结合经典设计模式,实现代码结构优化,提升可维护性与可读性。
      针对具体开发目标,定制化重构思路,满足业务需求与技术目标的双重要求。
      自动生成优化后代码片段,帮助开发者直接应用,提高开发效率。
      标注需要修改的关键部分,让代码优化过程更清晰、更直观。
      加速技术决策,为复杂项目提供设计模式参考与实践指导。
      帮助团队协作,创建更规范、更易维护的代码库。

      如何使用购买的提示词模板

      1. 直接在外部 Chat 应用中使用

      将模板生成的提示词复制粘贴到您常用的 Chat 应用(如 ChatGPT、Claude 等),即可直接对话使用,无需额外开发。适合个人快速体验和轻量使用场景。

      2. 发布为 API 接口调用

      把提示词模板转化为 API,您的程序可任意修改模板参数,通过接口直接调用,轻松实现自动化与批量处理。适合开发者集成与业务系统嵌入。

      3. 在 MCP Client 中配置使用

      在 MCP client 中配置对应的 server 地址,让您的 AI 应用自动调用提示词模板。适合高级用户和团队协作,让提示词在不同 AI 工具间无缝衔接。

      AI 提示词价格
      ¥20.00元
      先用后买,用好了再付款,超安全!

      您购买后可以获得什么

      获得完整提示词模板
      - 共 119 tokens
      - 5 个可调节参数
      { 编程语言 } { 代码片段 } { 设计模式名称 } { 优化目标 } { 代码复杂度 }
      获得社区贡献内容的使用权
      - 精选社区优质案例,助您快速上手提示词
      限时免费

      不要错过!

      免费获取高级提示词-优惠即将到期

      17
      :
      23
      小时
      :
      59
      分钟
      :
      59