编程思想之设计模式的思考

设计模式

当你的 if-else 长得像意大利面,策略模式来拯救你!🍝

大家好,我是检查员。今天不聊代码,我们来聊聊代码背后的”思想”。

你是否也曾面对过这样的代码:一个函数里,if-else if-else 像贪吃蛇一样越长越长,每次新增需求,都得小心翼翼地在蛇身上再加一节,生怕一不小心就把它弄断了?

这种代码,我们亲切地称之为”意大利面条”代码,或者更接地气一点——“屎山”💩。今天,我就分享一次亲身经历,如何用 策略模式(Strategy Pattern) 这把瑞士军刀,将一坨意大利面优雅地切分成精致的”菜品”。


😱 现状:一个”包办一切”的注册函数

想象一下,我们有一个统一的用户认证中心。一开始,它只为一个 App 服务,我们叫它”英语App”吧。注册逻辑很简单:

// 伪代码:最初的注册函数
function registerUser(userInfo) {
  // 1. 验证用户信息...
  // 2. 创建用户...
  
  // 3. 为"英语App"创建专属权限
  giveAppPermission(userInfo.id, '英语App');
  setTrialPeriod(userInfo.id, '英语App', 10); // 试用10天
  
  // 4. 返回成功
  return '注册成功!';
}

一切看起来都很美好。直到有一天,产品经理跑来和你说:“嘿!我们要做个新的’键盘App’,也要用这个注册功能!”

“小事一桩!” 你自信满满地加上了 if-else

// 伪代码:屎山初具规模
function registerUser(userInfo, appCode) {
  // 1. 验证用户信息...
  // 2. 创建用户...
  
  // 3. 根据 appCode 分配不同权限
  if (appCode === '英语App') {
    giveAppPermission(userInfo.id, '英语App');
    setTrialPeriod(userInfo.id, '英语App', 10);
  } else if (appCode === '键盘App') {
    giveAppPermission(userInfo.id, '键盘App');
    setTrialPeriod(userInfo.id, '键盘App', 7); // 键盘App试用期只有7天!
    // 键盘App还有个特别的邀请奖励逻辑
    handleKeyboardInvitation(userInfo);
  } else {
    // 如果没传 appCode,默认给英语App
    giveAppPermission(userInfo.id, '英语App');
    setTrialPeriod(userInfo.id, '英语App', 10);
  }
  
  // 4. 返回成功
  return '注册成功!';
}

现在,你能预见到未来吗?

  • 再来一个”数学App”? -> 再加一个 else if
  • “英语App”的邀请奖励逻辑改了? -> 深入到 if 的分支里去修改。
  • 代码越来越臃肿,违反了”开闭原则”(对扩展开放,对修改关闭)。每次修改都心惊胆战。

这就是我们面临的问题:一个核心功能,因为包含了太多本不属于它的、多变的业务细节,而变得僵硬和脆弱。


🤔 抉择:模板方法 vs. 策略模式

要解决这个问题,我们自然会想到设计模式。有两个候选人进入了决赛圈:

1. 模板方法模式 📜

这就像一个标准化的菜谱,规定了做菜的几个主要步骤(烧水 -> 下面 -> 加料 -> 出锅),但允许你在 加料 这一步自由发挥。

  • 做法:创建一个 AuthHandler 基类,里面定义好注册的整体流程(模板),然后把”为App创建权限”这个步骤做成一个抽象方法,让 EnglishAppAuthHandlerKeyboardAppAuthHandler 去继承和实现。
  • 优点:强制规定了算法的骨架,结构清晰。
  • 缺点:它是基于 继承 的。如果未来 App 的种类繁多,或者某个 App 的注册流程和”模板”略有不同,你就得创建一堆子类,或者把模板搞得更复杂。不够灵活!

2. 策略模式 🛠️

这就像一把瑞士军刀。刀柄(Context)是固定的,但你可以随时换上不同的工具头(Strategy),比如螺丝刀、剪刀、开瓶器。

  • 做法:定义一个 AuthStrategy 接口,里面有一个 handleRegistration 方法。然后为每个 App 创建一个实现了该接口的策略类。registerUser 函数不再关心具体逻辑,它只负责根据 appCode 找到对应的策略,然后执行它。
  • 优点:它是基于 组合 的。超级灵活!想增加一个新 App?简单,加一个新策略类就行,完全不用动原来的代码。你甚至可以在运行时动态切换策略!
  • 缺点:会增加一些”小零件”(接口、策略类、可能还有一个工厂类),初看起来比 if-else 复杂。

取舍: 对于我们这种”App注册逻辑”未来可能无限扩展的场景,灵活性 是王道。策略模式的”组合优于继承”思想完胜。我们不希望被一个僵化的”模板”束缚住手脚。所以,Pick 策略模式!


🚀 重构:三步”肢解”意大利面

我们的目标是把 registerUser 函数里那坨 if-else 彻底干掉!

第一步:定义”工具”的规格 (策略接口)

我们首先要明确,所有 App 特有的注册逻辑,都需要遵守一个统一的”规格”。

// 伪代码:策略接口
interface AuthStrategy {
  // 传入新用户和一些上下文信息
  handleRegistration(newUser, appCode, invitationCode);
}

第二步:打造每个 App 的专属”工具” (具体策略)

现在,我们把原来 if-else 里的逻辑,分别搬到各自的策略类里。

// 伪代码:英语App的策略
class EnglishAppStrategy implements AuthStrategy {
  handleRegistration(newUser, appCode, invitationCode) {
    // 这里是所有关于英语App的逻辑
    giveAppPermission(newUser.id, '英语App');
    setTrialPeriod(newUser.id, '英语App', 10);
    handleEnglishInvitation(invitationCode);
  }
}

// 伪代码:键盘App的策略
class KeyboardAppStrategy implements AuthStrategy {
  handleRegistration(newUser, appCode, invitationCode) {
    // 这里是所有关于键盘App的逻辑
    giveAppPermission(newUser.id, '键盘App');
    setTrialPeriod(newUser.id, '键盘App', 7);
    handleKeyboardInvitation(invitationCode);
  }
}

第三步:组装”瑞士军刀” (重构主流程)

最后,我们改造 registerUser 函数。它现在变成了一个”刀柄”,只负责两件事:

  1. 接收请求。
  2. 找到合适的”工具”(策略)来处理请求。
// 伪代码:重构后的注册函数 ✨

// 需要一个"策略提供者",它能根据 appCode 返回对应的策略实例
strategyProvider = new AuthStrategyProvider();

function registerUser(userInfo, appCode) {
  // 1. 验证用户信息... (通用逻辑保留)
  // 2. 创建用户... (通用逻辑保留)
  const newUser = createUser(userInfo);
  
  // 3. 核心变化在这里!
  // 从"工具箱"里拿出对应的工具
  const strategy = strategyProvider.getStrategy(appCode);
  
  // 4. 使用工具,具体怎么做,函数本身不关心
  strategy.handleRegistration(newUser, appCode, userInfo.invitationCode);
  
  // 5. 返回成功
  return '注册成功!';
}

看!registerUser 函数瞬间清爽了。它不再关心每个 App 的琐碎细节,只负责”调度”。


✨ 重构前后对比:一个新需求的到来

重构的好处,在面对变化时体现得淋漓尽致。

场景:产品经理又来了:“我们要上线’数学App’,试用期30天,邀请奖励双倍!”

😭 重构前 (意大利面时代)

  1. 你深吸一口气,打开那个巨大的 registerUser 函数。
  2. if...else if... 的长链末尾,小心翼翼地加上一段新的 else if (appCode === '数学App') { ... }
  3. 祈祷自己没有因为手抖,影响到其他 App 的逻辑。
  4. 测试时,需要把所有 App 的注册流程都回归一遍,因为你修改了公共的核心函数。

😎 重构后 (瑞士军刀时代)

  1. 你优雅地喝了口咖啡 ☕。

  2. 新建一个文件 MathAppStrategy.java

  3. 在里面实现 AuthStrategy 接口,写入”数学App”的专属逻辑。

    class MathAppStrategy implements AuthStrategy {
      handleRegistration(newUser, appCode, invitationCode) {
        giveAppPermission(newUser.id, '数学App');
        setTrialPeriod(newUser.id, '数学App', 30);
        handleDoubleInvitationReward(invitationCode);
      }
    }
  4. 完成! 你甚至没有碰 registerUser 函数一下。这就是 对修改关闭,对扩展开放 的真实写照。

总结

策略模式的核心思想是 “分离变化”。它将算法或行为(策略)从使用它的客户端(上下文)中分离出来,通过接口进行通信。这使得算法可以独立于客户端进行变化和扩展。

下次当你看到一个不断膨胀的 if-else 或者 switch 时,请停下来想一想:这里是不是有一族可以被”策略化”的行为?

用策略模式武装你的代码,告别面条,拥抱优雅!✨