目录

1.策略模式

1. 策略模式核心思想

2. 文章核心场景:商品差异化询价

原始需求

原始实现的问题

3. 策略模式重构步骤

策略模式的正式定义

4. 案例练习:用户登录方式验证

场景描述

TS 实现的优越性体现

2.状态模式

1. 核心场景:从“咖啡机需求”切入

2. 代码演进:告别 if-else,落地设计原则

3. 关键辨析:状态模式 vs 策略模式(孪生兄弟的区别)

状态模式核心定义

4. 案例练习:自动门控制(状态模式实践)

需求场景

TS 实现的核心优势总结

1.策略模式

1. 策略模式核心思想

  • 本质将「一系列可替换的算法/逻辑」封装成独立的“策略”,通过“策略分发器”动态选择使用哪种策略,替代臃肿的 if-else/switch 判断。
  • 核心目标:解耦“策略的定义”与“策略的使用”,满足单一职责原则(每个策略只做一件事)和开放封闭原则(新增策略无需修改原有代码,仅需扩展)。
  • 适用场景:当业务中存在多个“同类逻辑分支”(如不同计算规则、不同验证方式、不同处理流程),且可能频繁新增/修改分支时(如商品定价、登录验证、支付方式)。

2. 文章核心场景:商品差异化询价

原始需求

大促期间商品需按不同“价格标签”计算最终价,规则如下:

  • 预售价(pre):满100减20,不满100打9折
  • 大促价(onSale):满100减30,不满100打8折
  • 返场价(back):满200减50,不叠加
  • 尝鲜价(fresh):直接打5折
// 询价函数:一个函数处理4种逻辑,违背单一职责
function askPrice(tag, originPrice) {
  if (tag === 'pre') {
    return originPrice >= 100 ? originPrice - 20 : originPrice * 0.9;
  }
  if (tag === 'onSale') {
    return originPrice >= 100 ? originPrice - 30 : originPrice * 0.8;
  }
  if (tag === 'back') {
    return originPrice >= 200 ? originPrice - 50 : originPrice;
  }
  if (tag === 'fresh') {
    return originPrice * 0.5;
  }
}
原始实现的问题
  • 违背单一职责:一个函数承载4种询价逻辑,Bug定位难、复用性差(其他地方需用“预售价”逻辑时,只能复制代码)。
  • 违背开放封闭:新增价格标签(如“新人价”)时,必须修改 askPrice 函数,可能引入风险(需回归测试所有询价逻辑)。

3. 策略模式重构步骤

将每种询价逻辑拆分为独立“策略函数”,每个函数只处理一种规则:

// 策略1:预售价
function prePrice(originPrice) {
  return originPrice >= 100 ? originPrice - 20 : originPrice * 0.9;
}
// 策略2:大促价
function onSalePrice(originPrice) {
  return originPrice >= 100 ? originPrice - 30 : originPrice * 0.8;
}
// 策略3:返场价
function backPrice(originPrice) {
  return originPrice >= 200 ? originPrice - 50 : originPrice;
}
// 策略4:尝鲜价
function freshPrice(originPrice) {
  return originPrice * 0.5;
}

// 分发器:仅负责“选择策略”,不处理具体逻辑
function askPrice(tag, originPrice) {
  if (tag === 'pre') return prePrice(originPrice);
  if (tag === 'onSale') return onSalePrice(originPrice);
  if (tag === 'back') return backPrice(originPrice);
  if (tag === 'fresh') return freshPrice(originPrice);
}

用“对象映射”存储「标签-策略」的对应关系,彻底消除 if-else,新增策略时仅需扩展对象,无需修改分发器:

// 策略集合:存储所有询价策略(键=标签,值=策略函数)
const priceStrategies = {
  pre: (originPrice) => originPrice >= 100 ? originPrice - 20 : originPrice * 0.9,
  onSale: (originPrice) => originPrice >= 100 ? originPrice - 30 : originPrice * 0.8,
  back: (originPrice) => originPrice >= 200 ? originPrice - 50 : originPrice,
  fresh: (originPrice) => originPrice * 0.5
};

// 分发器:通过对象映射动态选择策略,代码永久不变
function askPrice(tag, originPrice) {
  // 若标签不存在,返回原价(容错处理)
  return priceStrategies[tag]?.(originPrice) || originPrice;
}

// 新增“新人价”策略:仅需扩展策略集合,无需修改askPrice
priceStrategies.newUser = (originPrice) => originPrice >= 100 ? originPrice - 50 : originPrice;

策略模式的正式定义

定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。

  • “算法”:不仅指数学计算,也可指业务逻辑(如询价规则、登录验证逻辑)。
  • “封装”:每个策略独立成函数/类,与其他策略解耦。
  • “可替换”:通过分发器(如对象映射)动态切换策略,无需修改使用逻辑。

4. 案例练习:用户登录方式验证

场景描述

实现用户登录验证功能,支持3种登录方式,每种方式的验证规则不同:

  1. 账号密码登录(account):验证用户名不为空 + 密码长度≥6位
  2. 手机号验证码登录(phone):验证手机号格式(11位数字) + 验证码为6位数字
  3. 第三方登录(thirdParty):验证第三方平台标识(如wechat/github)不为空 + 授权码不为空

要求:新增登录方式时无需修改原有验证逻辑,且避免 if-else

// 1. 定义所有登录策略(封装独立规则)
const loginStrategies = {
  // 策略1:账号密码登录
  account: (data) => {
    if (!data.username) return { valid: false, msg: '用户名不能为空' };
    if (data.password.length < 6) return { valid: false, msg: '密码长度需≥6位' };
    return { valid: true, msg: '账号密码验证通过' };
  },
  // 策略2:手机号验证码登录
  phone: (data) => {
    const phoneReg = /^1\d{10}$/;
    if (!phoneReg.test(data.phone)) return { valid: false, msg: '手机号格式错误' };
    if (!/^\d{6}$/.test(data.code)) return { valid: false, msg: '验证码需为6位数字' };
    return { valid: true, msg: '手机号验证码验证通过' };
  },
  // 策略3:第三方登录
  thirdParty: (data) => {
    if (!['wechat', 'github', 'google'].includes(data.platform)) return { valid: false, msg: '不支持的第三方平台' };
    if (!data.authCode) return { valid: false, msg: '授权码不能为空' };
    return { valid: true, msg: '第三方登录验证通过' };
  }
};

// 2. 登录验证分发器(使用策略)
function verifyLogin(type, data) {
  // 容错:若登录方式不存在,直接返回无效
  if (!loginStrategies[type]) return { valid: false, msg: '不支持的登录方式' };
  // 调用对应策略进行验证
  return loginStrategies[type](data);
}

// 3. 测试
console.log(verifyLogin('account', { username: 'zhangsan', password: '123456' })); 
// { valid: true, msg: '账号密码验证通过' }
console.log(verifyLogin('phone', { phone: '13800138000', code: '123456' })); 
// { valid: true, msg: '手机号验证码验证通过' }
// 新增“邮箱登录”策略(无需修改verifyLogin)
loginStrategies.email = (data) => {
  const emailReg = /^[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-zA-Z]+$/;
  if (!emailReg.test(data.email)) return { valid: false, msg: '邮箱格式错误' };
  return { valid: true, msg: '邮箱登录验证通过' };
};
console.log(verifyLogin('email', { email: 'test@example.com' })); 
// { valid: true, msg: '邮箱登录验证通过' }
// 1. 定义类型约束:明确登录数据结构(避免传错参数)
interface AccountLoginData {
  username: string;
  password: string;
}

interface PhoneLoginData {
  phone: string;
  code: string;
}

interface ThirdPartyLoginData {
  platform: 'wechat' | 'github' | 'google'; // 限定第三方平台范围
  authCode: string;
}

interface EmailLoginData {
  email: string;
}

// 2. 定义策略接口:规范所有登录策略的“输入输出”(强制统一结构)
interface LoginStrategy<T> {
  verify: (data: T) => { valid: boolean; msg: string };
}

// 3. 定义登录类型枚举:限定仅支持的登录方式(避免传错字符串,如"accout"拼写错误)
enum LoginType {
  ACCOUNT = 'account',
  PHONE = 'phone',
  THIRD_PARTY = 'thirdParty',
  EMAIL = 'email'
}

// 4. 实现具体登录策略(TS自动校验:必须符合LoginStrategy接口)
const AccountStrategy: LoginStrategy<AccountLoginData> = {
  verify: (data) => {
    if (!data.username) return { valid: false, msg: '用户名不能为空' };
    if (data.password.length < 6) return { valid: false, msg: '密码长度需≥6位' };
    return { valid: true, msg: '账号密码验证通过' };
  }
};

const PhoneStrategy: LoginStrategy<PhoneLoginData> = {
  verify: (data) => {
    const phoneReg = /^1\d{10}$/;
    if (!phoneReg.test(data.phone)) return { valid: false, msg: '手机号格式错误' };
    if (!/^\d{6}$/.test(data.code)) return { valid: false, msg: '验证码需为6位数字' };
    return { valid: true, msg: '手机号验证码验证通过' };
  }
};

const ThirdPartyStrategy: LoginStrategy<ThirdPartyLoginData> = {
  verify: (data) => {
    if (!data.platform) return { valid: false, msg: '第三方平台不能为空' };
    if (!data.authCode) return { valid: false, msg: '授权码不能为空' };
    return { valid: true, msg: '第三方登录验证通过' };
  }
};

// 5. 策略集合:类型明确(键=LoginType,值=对应策略)
const loginStrategyMap: Record<LoginType, LoginStrategy<any>> = {
  [LoginType.ACCOUNT]: AccountStrategy,
  [LoginType.PHONE]: PhoneStrategy,
  [LoginType.THIRD_PARTY]: ThirdPartyStrategy,
  // 初始未定义EMAIL,TS会提示“属性EMAIL缺失”(编译时校验)
};

// 6. 登录验证分发器(类型安全:参数和返回值明确)
function verifyLogin<T>(type: LoginType, data: T): { valid: boolean; msg: string } {
  const strategy = loginStrategyMap[type];
  if (!strategy) return { valid: false, msg: '不支持的登录方式' };
  // TS自动校验:strategy.verify的参数必须是T类型
  return strategy.verify(data);
}

// 7. 测试(TS实时提示参数错误,编译时拦截问题)
// 正确案例:账号密码登录(参数符合AccountLoginData)
console.log(verifyLogin(LoginType.ACCOUNT, { username: 'zhangsan', password: '123456' }));
// 错误案例1:手机号登录少传code(TS提示“Property 'code' is missing”)
// console.log(verifyLogin(LoginType.PHONE, { phone: '13800138000' }));
// 错误案例2:第三方登录传无效platform(TS提示“Type 'alipay' is not assignable to type 'wechat' | 'github' | 'google'”)
// console.log(verifyLogin(LoginType.THIRD_PARTY, { platform: 'alipay', authCode: 'xxx' }));

// 8. 新增“邮箱登录”策略(仅需扩展,无需修改原有代码)
const EmailStrategy: LoginStrategy<EmailLoginData> = {
  verify: (data) => {
    const emailReg = /^[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-zA-Z]+$/;
    if (!emailReg.test(data.email)) return { valid: false, msg: '邮箱格式错误' };
    return { valid: true, msg: '邮箱登录验证通过' };
  }
};
// 扩展策略集合(TS自动校验:必须符合LoginStrategy<EmailLoginData>)
loginStrategyMap[LoginType.EMAIL] = EmailStrategy;
// 测试邮箱登录
console.log(verifyLogin(LoginType.EMAIL, { email: 'test@example.com' }));

TS 实现的优越性体现

  1. 类型安全:通过接口(LoginStrategy)、枚举(LoginType)和类型注解,强制约束“策略的输入输出”和“登录方式范围”,避免拼写错误(如"accout")、少传参数(如手机号登录少传code)、传无效值(如第三方平台传alipay),这些错误在编译时即可发现,而JS需运行时才暴露。
  2. 开发体验:IDE会提供实时自动提示(如输入LoginType.时,自动显示所有支持的登录方式;调用verifyLogin时,提示参数结构),无需记忆参数格式,降低协作成本。
  3. 可维护性:类型定义清晰描述了每个策略的依赖(如AccountLoginDatausernamepassword),新开发者无需阅读业务逻辑即可理解参数含义,且新增策略时TS会自动校验是否符合规范(如必须实现verify方法)。

2.状态模式

1. 核心场景:从“咖啡机需求”切入

文章以“李雷实现咖啡机功能”为案例,明确状态模式的适用场景:
当一个对象(如咖啡机)有多种互斥状态(美式、拿铁、香草拿铁、摩卡),且不同状态对应不同行为逻辑(制作咖啡的步骤),同时状态间需要灵活切换时,适合用状态模式解决。

2. 代码演进:告别 if-else,落地设计原则

状态模式的实现核心是“将状态判断逻辑拆分让行为与状态解耦”,文章通过3版代码逐步落地:

直接在状态切换函数中用 if-else 嵌套所有行为逻辑,存在两大问题:

  • 行为逻辑无法复用(如香草拿铁依赖拿铁的“加奶”步骤,却需重复写);
  • 违反“开放封闭原则”(新增咖啡品种需修改 changeState 函数)。
class CoffeeMaker {
  constructor() {
    this.state = 'init'; // 初始状态
  }
  changeState(state) {
    this.state = state;
    if (state === 'american') console.log('只吐黑咖啡');
    else if (state === 'latte') console.log('黑咖啡加点奶');
    else if (state === 'vanillaLatte') console.log('黑咖啡加点奶再加香草糖浆');
    else if (state === 'mocha') console.log('黑咖啡加点奶再加巧克力');
  }
}

将“咖啡制作行为”抽离为独立方法,在 changeState 中调用方法,实现行为复用(如 vanillaLatteProcress 可调用 latteProcress),但仍未解决 if-else 判断问题。

class CoffeeMaker {
  constructor() { this.state = 'init'; }
  changeState(state) {
    this.state = state;
    state === 'american' && this.americanProcess();
    state === 'latte' && this.latteProcress();
    // ...其他状态判断
  }
  // 抽离行为方法
  americanProcess() { console.log('只吐黑咖啡'); }
  latteProcress() { this.americanProcess(); console.log('加点奶'); }
  vanillaLatteProcress() { this.latteProcress(); console.log('再加香草糖浆'); }
}

用“状态-行为映射对象”替代 if-else,同时让映射对象关联实例(通过 that: this),解决“行为感知实例内部状态”的问题(如读取咖啡机牛奶储量),完全符合开放封闭原则(新增品种只需在映射对象中加方法)。

class CoffeeMaker {
  constructor() {
    this.state = 'init';
    this.leftMilk = '500ml'; // 实例内部状态(牛奶储量)
    // 状态-行为映射对象(关联实例)
    this.stateToProcessor = {
      that: this,
      american() {
        console.log('牛奶储量:', this.that.leftMilk);
        console.log('只吐黑咖啡');
      },
      latte() { this.american(); console.log('加点奶'); },
      vanillaLatte() { this.latte(); console.log('再加香草糖浆'); },
      mocha() { this.latte(); console.log('再加巧克力'); }
    };
  }
  changeState(state) {
    this.state = state;
    this.stateToProcessor[state]?.(); // 委托映射对象执行行为
  }
}

3. 关键辨析:状态模式 vs 策略模式(孪生兄弟的区别)

两者都封装行为、通过委托分发逻辑,但核心差异在行为与主体的关联

维度

策略模式

状态模式

行为独立性

行为(策略)互相平行,不依赖主体

行为(状态逻辑)依赖主体,需感知主体内部状态

行为目的

解决“多种算法选择”(如不同排序算法)

解决“多种状态切换”(如咖啡机不同制作流程)

主体与行为关系

主体主动选择策略

主体状态变化触发行为切换

状态模式核心定义

  • 官方定义:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
  • 通俗理解:把对象的“状态判断”拆成独立的行为逻辑,让状态变化时自动切换行为,而非用 if-else 硬编码。

4. 案例练习:自动门控制(状态模式实践)

需求场景

设计一个自动门系统,包含以下状态和行为:

  1. 状态:关闭状态(默认)、开启状态、延迟关闭状态(门开后停留3秒再关);
  2. 触发事件
    • 有人靠近(关闭状态 → 开启状态);
    • 开门后超时(开启状态 → 延迟关闭状态 → 关闭状态);
    • 紧急按钮(任何状态 → 关闭状态)。
class AutoDoor {
  constructor() {
    this.currentState = 'closed'; // 初始状态:关闭
    this.closeDelay = 3000; // 延迟关闭时间(3秒)
    this.timer = null; // 超时定时器

    // 状态-行为映射对象(关联实例)
    this.stateToAction = {
      that: this,
      // 关闭状态:仅响应“有人靠近”
      closed() {
        console.log('门已关闭,等待触发...');
        this.that.onPersonApproach = () => {
          this.that.switchState('opened');
        };
      },
      // 开启状态:触发延迟关闭
      opened() {
        clearTimeout(this.that.timer);
        console.log('门已开启,3秒后自动关闭...');
        this.that.timer = setTimeout(() => {
          this.that.switchState('delayedClose');
        }, this.that.closeDelay);
        // 紧急按钮:强制关闭
        this.that.onEmergency = () => {
          this.that.switchState('closed');
        };
      },
      // 延迟关闭状态:执行关闭
      delayedClose() {
        clearTimeout(this.that.timer);
        console.log('延迟时间到,门正在关闭...');
        this.that.switchState('closed');
      }
    };

    // 初始化状态
    this.switchState('closed');
  }

  // 状态切换方法
  switchState(state) {
    this.currentState = state;
    this.stateToAction[state]?.();
  }

  // 模拟外部触发:有人靠近
  triggerApproach() {
    this.onPersonApproach?.();
  }

  // 模拟外部触发:紧急按钮
  triggerEmergency() {
    this.onEmergency?.();
  }
}

// 测试
const door = new AutoDoor(); // 输出:门已关闭,等待触发...
door.triggerApproach(); // 输出:门已开启,3秒后自动关闭...
// 3秒后输出:延迟时间到,门正在关闭... → 门已关闭,等待触发...
// door.triggerEmergency(); // 紧急按钮:立即关闭

TS 优势体现在:类型约束(避免魔法字符串/错误调用)、枚举(状态语义化)、接口(行为结构统一),编译时即可发现错误。

// 1. 枚举:状态语义化,避免魔法字符串
enum DoorState {
  CLOSED = 'closed',
  OPENED = 'opened',
  DELAYED_CLOSE = 'delayedClose'
}

// 2. 接口:约束状态-行为映射对象的结构,确保每个状态有对应方法
interface DoorActionMap {
  [key in DoorState]: () => void;
  that: AutoDoor; // 关联实例
}

class AutoDoor {
  // 3. 类型标注:明确属性类型,避免类型错误
  public currentState: DoorState = DoorState.CLOSED;
  public closeDelay: number = 3000;
  public timer: NodeJS.Timeout | null = null;
  // 4. 类型标注:行为触发函数(明确参数/返回值类型)
  public onPersonApproach?: () => void;
  public onEmergency?: () => void;

  // 状态-行为映射对象(TS 自动校验结构是否符合 DoorActionMap)
  public stateToAction: DoorActionMap = {
    that: this,
    [DoorState.CLOSED]: () => {
      console.log('门已关闭,等待触发...');
      this.onPersonApproach = () => {
        this.switchState(DoorState.OPENED); // 只能传入 DoorState 枚举值
      };
    },
    [DoorState.OPENED]: () => {
      clearTimeout(this.timer!); // 非空断言(TS 知道 timer 已初始化)
      console.log('门已开启,3秒后自动关闭...');
      this.timer = setTimeout(() => {
        this.switchState(DoorState.DELAYED_CLOSE);
      }, this.closeDelay);
      this.onEmergency = () => {
        this.switchState(DoorState.CLOSED);
      };
    },
    [DoorState.DELAYED_CLOSE]: () => {
      clearTimeout(this.timer!);
      console.log('延迟时间到,门正在关闭...');
      this.switchState(DoorState.CLOSED);
    }
  };

  constructor() {
    this.switchState(DoorState.CLOSED);
  }

  // 状态切换方法(参数只能是 DoorState 类型)
  public switchState(state: DoorState): void {
    this.currentState = state;
    this.stateToAction[state](); // TS 确保 state 一定在映射对象中存在
  }

  // 外部触发方法(类型明确,避免错误调用)
  public triggerApproach(): void {
    this.onPersonApproach?.();
  }

  public triggerEmergency(): void {
    this.onEmergency?.();
  }
}

// 测试(TS 自动提示枚举值,避免传错状态)
const door = new AutoDoor();
door.triggerApproach();
// door.switchState('error'); // 编译报错:'error' 不是 DoorState 类型
// door.triggerEmergency(123); // 编译报错:triggerEmergency 无参数

TS 实现的核心优势总结

  1. 枚举(DoorState):限定状态取值范围,避免传入无效字符串(如 'error');
  2. 接口(DoorActionMap):强制状态-行为映射对象的结构,确保每个状态有对应方法;
  3. 类型标注:明确属性/方法的类型(如 timer: NodeJS.Timeout | null),编译时发现类型错误;
  4. 语义化:代码可读性更高,协作时他人可通过类型快速理解状态和行为的关联。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐