
预估阅读: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)
提交代码。
「搞定了。」
产品经理:「这么快?」
小禾端起咖啡:「代码架构对了,需求就不是事儿。」
一句话总结
设计模式的价值,不在于代码变少,而在于改动变少。
当你能做到「加功能只需加代码,不需改代码」的时候,你就真正理解了设计模式的精髓。
而这,也是区分「写代码」和「做工程」的分水岭。