策略模式是消除 if-else/switch 的经典手段:把每种算法/逻辑封装成独立的策略函数,用一个注册表(Record)把策略名映射到策略函数,执行时根据名称查表调用。TypeScript 下的策略模式可以用 Record 做类型化注册表,用泛型约束策略参数和返回值,新增策略只需加一条记录而无需修改执行器,完美符合开闭原则。表单校验、折扣计算、支付方式、文件上传等场景都非常适合用策略模式重构。
// 策略模式:策略注册表 + 执行器,Record 映射策略名到策略函数
// 泛型约束策略参数和返回值,消除 if-else,符合开闭原则
// 1. 策略函数类型:输入参数 + 返回值
type Strategy<TParams = any, TResult = any> =
(params: TParams) => TResult;
// 2. 策略注册表:策略名 → 策略函数
type StrategyRegistry<K extends string = string, P = any, R = any> =
Record<K, Strategy<P, R>>;
// 3. 策略执行器:根据策略名执行对应策略
class StrategyExecutor<K extends string, P, R> {
private strategies: StrategyRegistry<K, P, R>;
constructor(strategies: StrategyRegistry<K, P, R>) {
this.strategies = strategies;
}
// 注册新策略
register(name: K, strategy: Strategy<P, R>): void {
this.strategies[name] = strategy;
}
// 执行策略
execute(name: K, params: P): R {
const strategy = this.strategies[name];
if (!strategy) {
throw new Error(`Strategy ${name} not found`);
}
return strategy(params);
}
// 检查策略是否存在
has(name: string): name is K {
return name in this.strategies;
}
// 获取所有策略名
getStrategyNames(): K[] {
return Object.keys(this.strategies) as K[];
}
}
// 4. 实战示例:折扣计算器
// 折扣类型
type DiscountType = 'none' | 'percentage' | 'fixed' | 'vip';
// 折扣参数
interface DiscountParams {
originalPrice: number;
discountValue: number; // 百分比值 / 固定减免金额 / VIP 折扣率
}
// 折扣策略注册表
const discountStrategies: StrategyRegistry<DiscountType, DiscountParams, number> = {
none: ({ originalPrice }) => originalPrice,
percentage: ({ originalPrice, discountValue }) => originalPrice * (discountValue / 100),
fixed: ({ originalPrice, discountValue }) => Math.max(0, originalPrice - discountValue),
vip: ({ originalPrice, discountValue }) => originalPrice * (discountValue / 100) * 0.95, // VIP 折上折
};
// 创建折扣计算器
const discountCalculator = new StrategyExecutor(discountStrategies);
// 5. 实战示例:表单校验策略
type ValidatorType = 'required' | 'email' | 'phone' | 'minLength';
interface ValidatorParams {
value: string;
minLength?: number;
}
interface ValidatorResult {
valid: boolean;
message: string;
}
const validatorStrategies: StrategyRegistry<ValidatorType, ValidatorParams, ValidatorResult> = {
required: ({ value }) => ({
valid: value.trim().length > 0,
message: '该字段不能为空',
}),
email: ({ value }) => ({
valid: /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
message: '请输入有效的邮箱地址',
}),
phone: ({ value }) => ({
valid: /^1\d{10}$/.test(value),
message: '请输入有效的手机号',
}),
minLength: ({ value, minLength = 0 }) => ({
valid: value.length >= minLength,
message: `长度至少为 ${minLength} 个字符`,
}),
};
const validatorExecutor = new StrategyExecutor(validatorStrategies);
// 使用
const price = discountCalculator.execute('percentage', {
originalPrice: 100,
discountValue: 80,
}); // = 80
const emailResult = validatorExecutor.execute('email', {
value: 'test@example.com',
}); // = { valid: true, message: '...' }
Record<K, Strategy<P, R>> 定义策略注册表,K 是策略名联合类型,键必须是 K 中的值,传错策略名 TS 直接报错,比对象字面量更安全。StrategyExecutor<K, P, R> 三个泛型分别约束策略名、参数、返回值,策略的输入输出类型统一管理,新增策略时自动受相同类型约束。has(name): name is K 用类型守卫收窄参数类型,检查通过后 name 的类型从 string 收窄为 K,调用 execute 时类型安全。// 策略名有类型提示和约束
const result = discountCalculator.execute('none', {
originalPrice: 200,
discountValue: 0,
});
// result: number
// 传错策略名会报错
// discountCalculator.execute('unknown', { ... }); // 错误
// 运行时检查 + 类型收窄
function safeExecute(name: string, params: DiscountParams) {
if (discountCalculator.has(name)) {
// 这里 name 的类型被收窄为 DiscountType
return discountCalculator.execute(name, params);
}
throw new Error('未知的折扣类型');
}
// 获取所有策略名
const allDiscounts = discountCalculator.getStrategyNames();
// => ['none', 'percentage', 'fixed', 'vip']
// 类型:DiscountType[]
// 运行时注册新策略
discountCalculator.register('secondHalf', ({ originalPrice, discountValue }) => {
const fullPrice = Math.floor(originalPrice / 2);
const halfPrice = originalPrice - fullPrice;
return fullPrice + halfPrice * (discountValue / 100);
});
// 动态注册后就可以 execute('secondHalf', ...)
// 对比:传统 if-else 写法
function calcDiscount(type: string, price: number, value: number): number {
if (type === 'none') return price;
if (type === 'percentage') return price * (value / 100);
if (type === 'fixed') return Math.max(0, price - value);
if (type === 'vip') return price * (value / 100) * 0.95;
throw new Error('unknown type');
}
// 缺点:每加一个策略就要修改函数,违反开闭原则
// 策略模式:只需 register 注册,执行器代码不动
// 实战:表单校验函数
function validateField(value: string, rules: ValidatorType[]): ValidatorResult {
for (const rule of rules) {
const result = validatorExecutor.execute(rule, { value, minLength: 6 });
if (!result.valid) return result;
}
return { valid: true, message: '' };
}
const r = validateField('abc123', ['required', 'minLength']);
// r.valid = true