编程思想之设计模式的思考
当你的 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创建权限”这个步骤做成一个抽象方法,让EnglishAppAuthHandler和KeyboardAppAuthHandler去继承和实现。 - 优点:强制规定了算法的骨架,结构清晰。
- 缺点:它是基于 继承 的。如果未来 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 函数。它现在变成了一个”刀柄”,只负责两件事:
- 接收请求。
- 找到合适的”工具”(策略)来处理请求。
// 伪代码:重构后的注册函数 ✨
// 需要一个"策略提供者",它能根据 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天,邀请奖励双倍!”
😭 重构前 (意大利面时代)
- 你深吸一口气,打开那个巨大的
registerUser函数。 - 在
if...else if...的长链末尾,小心翼翼地加上一段新的else if (appCode === '数学App') { ... }。 - 祈祷自己没有因为手抖,影响到其他 App 的逻辑。
- 测试时,需要把所有 App 的注册流程都回归一遍,因为你修改了公共的核心函数。
😎 重构后 (瑞士军刀时代)
-
你优雅地喝了口咖啡 ☕。
-
新建一个文件
MathAppStrategy.java。 -
在里面实现
AuthStrategy接口,写入”数学App”的专属逻辑。class MathAppStrategy implements AuthStrategy { handleRegistration(newUser, appCode, invitationCode) { giveAppPermission(newUser.id, '数学App'); setTrialPeriod(newUser.id, '数学App', 30); handleDoubleInvitationReward(invitationCode); } } -
完成! 你甚至没有碰
registerUser函数一下。这就是 对修改关闭,对扩展开放 的真实写照。
总结
策略模式的核心思想是 “分离变化”。它将算法或行为(策略)从使用它的客户端(上下文)中分离出来,通过接口进行通信。这使得算法可以独立于客户端进行变化和扩展。
下次当你看到一个不断膨胀的 if-else 或者 switch 时,请停下来想一想:这里是不是有一族可以被”策略化”的行为?
用策略模式武装你的代码,告别面条,拥抱优雅!✨