改一个需求动 23 处代码?你可能踩进了这个坑

预估阅读:10 分钟


一个真实的踩坑故事

小禾有个朋友,也叫小禾。

小禾最近接了个活儿,做一个电商支付模块。一开始需求简单:接入支付宝。

python 复制代码
def pay(order):
    return alipay.create_payment(order)

清清爽爽,一行搞定。


第一次加需求

一周后,产品经理说:「微信支付也要有,用户喜欢选择。」

小禾:简单。

python 复制代码
def pay(order, method="alipay"):
    if method == "alipay":
        return alipay.create_payment(order)
    else:
        return wechat.create_payment(order)

两个分支,小事。


第二次加需求

两周后:「银联也加上吧,企业客户要用。」

python 复制代码
def pay(order, method="alipay"):
    if method == "alipay":
        return alipay.create_payment(order)
    elif method == "wechat":
        return wechat.create_payment(order)
    elif method == "unionpay":
        return unionpay.create_payment(order)

小禾眉头微皱,但还是忍了。


第三次加需求

又过了一个月:「Apple Pay 和 PayPal 也要支持,海外用户需要。」

小禾深吸一口气,打开代码库。

然后他发现,if method == "xxx" 这个判断不只在一个地方。

pay() 里有。 在 refund() 里有。 在 get_payment_name() 里有。 在 check_available() 里也有。 在 calculate_fee() 里还有。

一共 8 个文件,23 处判断。

小禾盯着屏幕,陷入沉思:

「这代码,还能救吗?」


散弹枪代码:改一处要动十处

小禾的遭遇,你可能也经历过。

我给这种代码起了个名字:散弹枪代码

为什么叫这个名字?因为改一个需求,像打散弹枪一样,要击中十几个地方。漏了一个,线上炸。

它有几个典型症状:

症状一:修改时全局搜索

新人问:「我要加个新支付方式,改哪里?」

你答:「全局搜 if method,搜到的地方都要改。」

新人眼里的光暗了。

症状二:复制粘贴蔓延

python 复制代码
# file1.py
if method == "alipay": ...
elif method == "wechat": ...

# file2.py(复制粘贴)
if method == "alipay": ...
elif method == "wechat": ...

# file3.py(再复制)
if method == "alipay": ...
elif method == "wechat": ...

这不是代码复用,这是 bug 复制。

症状三:漏改导致线上事故

小禾后来查了一个 bug:某个支付方式退款失败。

原因是 refund() 函数里漏了一个分支------复制的时候少粘贴了一段。

那晚他加班到凌晨三点。


问题的本质:决策权散落各处

让我们思考一个问题:

「用哪个支付方式」这个决策,应该由谁来做?

散弹枪代码的问题在于:每个函数都在自己做这个决策。

  • pay() 函数决定一次
  • refund() 函数又决定一次
  • get_payment_name() 再决定一次

同一个决策,重复做了 23 次。

自然会出问题。

正确的做法是:把决策权收归一处。


工厂模式:一处决策,处处复用

打个比方。

你去餐厅点餐,只需要说「来份宫保鸡丁」。

你不需要知道厨房用什么锅、什么油、什么火候。厨房就是「工厂」,你只管点菜和吃。

代码也一样。业务逻辑只需要说「给我一个支付处理器」,不需要关心是支付宝还是微信。

这就是工厂模式的核心思想。


第一步:集中决策

最简单的改法:

python 复制代码
class PaymentFactory:
    @staticmethod
    def create(method: str):
        if method == "alipay":
            return AlipayProcessor()
        elif method == "wechat":
            return WechatProcessor()
        elif method == "unionpay":
            return UnionPayProcessor()
        else:
            raise ValueError(f"不支持的支付方式: {method}")

业务代码变成:

python 复制代码
def pay(order, method="alipay"):
    processor = PaymentFactory.create(method)
    return processor.pay(order)

def refund(order, method):
    processor = PaymentFactory.create(method)
    return processor.refund(order)

等等,工厂里不还是 if-else 吗?

是的。但关键区别在于:

if-else 只在工厂里出现一次,不再散落各处。

新增支付方式?只改工厂这一个文件。其他 7 个文件一行不动。


第二步:注册机制

工厂里的 if-else,能不能也消灭掉?

可以。用注册表

python 复制代码
class PaymentFactory:
    _processors = {}  # 注册表

    @classmethod
    def register(cls, name: str, processor_class):
        """注册一个支付处理器"""
        cls._processors[name] = processor_class

    @classmethod
    def create(cls, name: str):
        """创建支付处理器实例"""
        if name not in cls._processors:
            available = list(cls._processors.keys())
            raise ValueError(f"不支持 {name},可用: {available}")
        return cls._processors[name]()

各支付方式自己注册:

python 复制代码
# alipay_processor.py
class AlipayProcessor:
    def pay(self, order): ...
    def refund(self, order): ...

PaymentFactory.register("alipay", AlipayProcessor)

# wechat_processor.py
class WechatProcessor:
    def pay(self, order): ...
    def refund(self, order): ...

PaymentFactory.register("wechat", WechatProcessor)

新增 PayPal?创建一个新文件,写一行注册代码,完事。

工厂代码?一个字都不用改。

这就是「开闭原则」:对扩展开放,对修改关闭。


AI 项目更需要这个

如果你做 AI 相关的项目,这个模式更加重要。

因为 AI 模型有个特点:重且易变

  • 今天老板说用 GPT-4
  • 明天说太贵,换 Claude
  • 后天又说试试开源的 Llama
  • 大后天发现某个场景还是 GPT-4 效果好

如果你的代码散落着 if model == "gpt4" 这样的判断......

祝你好运。

正确的做法

python 复制代码
class LLMFactory:
    _adapters = {}
    _instance = None

    @classmethod
    def register(cls, name, adapter_class):
        cls._adapters[name] = adapter_class

    @classmethod
    def get(cls):
        """单例获取,避免重复初始化"""
        if cls._instance is None:
            model = config.LLM_MODEL
            cls._instance = cls._adapters[model]()
        return cls._instance

    @classmethod
    def reset(cls):
        """切换模型时重置"""
        if cls._instance:
            cls._instance.cleanup()  # 释放显存!
        cls._instance = None

为什么要单例?因为 AI 模型加载一次可能要:

  • 30 秒启动时间
  • 10GB 显存
  • 一堆 CUDA 初始化

每次请求都重新加载?用户会疯。


什么时候该用

场景 建议
只有 2-3 种类型,以后不会加 if-else 足够
类型会扩展 注册式工厂
对象创建成本高 工厂 + 单例
大型项目 分层工厂

最重要的一点:别过度设计。

如果你的项目就 2 个选项,以后也不会有第 3 个,那 if-else 完全没问题。

工厂模式是用来解决「类型会变多」这个问题的。问题不存在,解决方案也不需要存在。


故事的结尾

小禾用一个周末重构了代码。

把 23 处 if-else 收归到了一处。

周一产品经理又来:「小禾啊,数字人民币也要支持,政策要求。」

小禾微微一笑,新建了一个 dcep_processor.py

python 复制代码
class DCEPProcessor:
    def pay(self, order): ...
    def refund(self, order): ...

PaymentFactory.register("dcep", DCEPProcessor)

提交代码。

「搞定了。」

产品经理:「这么快?」

小禾端起咖啡:「代码架构对了,需求就不是事儿。」


一句话总结

设计模式的价值,不在于代码变少,而在于改动变少。

当你能做到「加功能只需加代码,不需改代码」的时候,你就真正理解了设计模式的精髓。

而这,也是区分「写代码」和「做工程」的分水岭。

相关推荐
踏浪无痕5 小时前
自定义 ClassLoader 动态加载:不重启就能加载新代码?
后端·面试·架构
踏浪无痕5 小时前
别重蹈我们的覆辙:脚本引擎选错的两年代价
后端·面试·架构
何中应5 小时前
【面试题-4】JVM
java·jvm·后端·面试题
Oneslide5 小时前
如何在Kubernetes搭建RabbitMQ集群 部署篇
后端
VX:Fegn08955 小时前
计算机毕业设计|基于springboot + vue非遗传承文化管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
tonydf5 小时前
从零开始玩转 Microsoft Agent Framework:我的 MAF 实践之旅
后端·aigc
sptan5 小时前
Nacos适用Postgresql改造记录
后端
okseekw5 小时前
Java网络编程从入门到实战:吃透三要素,玩转CS/BS架构
java·后端·http
我是你们的明哥6 小时前
A*(A-Star)算法详解:智能路径规划的核心技术
后端·算法