订单状态又被改乱了。
日志里一看,很熟悉:
refund failed, order_id=202406120091, status=paid, amount=-30
金额能是负数,已支付订单还能绕过校验直接退款。代码翻出来也不复杂,就是一个大字典到处传:
order = {
"order_id": "202406120091",
"user_id": 18,
"amount": -30,
"status": "paid"
}
这种写法刚开始很爽,字段想加就加,想改就改。
问题也在这里。
谁都能改,哪里都能改,最后就没人知道这个对象到底还干不干净。
Python 的 OOPS,也就是面向对象编程,别一上来就背“封装、继承、多态”。那套话没错,但太干了。放到项目里看,它解决的就是一件事:把一坨到处乱飞的数据,变成一个有边界、有行为、有规矩的对象。
比如订单,不应该只是几个字段。
订单自己就应该知道:什么金额合法,什么状态能退款,什么时候不能改。
我一般会先把这种散落的判断收回来:
classPayOrder:
def__init__(self, order_id, user_id, amount):
if amount <= 0:
raise ValueError(f"订单金额不合法: {amount}")
self.order_id = order_id
self.user_id = user_id
self.amount = amount
self._status = "created"
@property
defstatus(self):
return self._status
defmark_paid(self):
if self._status != "created":
raise RuntimeError(f"当前状态不能支付: {self._status}")
self._status = "paid"
defrefund(self):
if self._status != "paid":
raise RuntimeError(f"当前状态不能退款: {self._status}")
self._status = "refunded"
return {
"order_id": self.order_id,
"refund_amount": self.amount,
"user_id": self.user_id
}
这段代码不花哨,但比到处写 if order["status"] == xxx 强多了。
封装不是把字段藏起来显得高级,而是把不该乱改的地方收住。比如 _status 前面这个下划线,不是 Python 强制不让你改,而是在提醒后面接手的人:别手贱直接改它,走方法。
用的时候也直观:
order = PayOrder("202406120091", 18, 99)
order.mark_paid()
refund_bill = order.refund()
print(refund_bill)
状态流转在哪里?在 PayOrder 里面。
金额校验在哪里?也在 PayOrder 里面。
以后退款规则变了,不用全项目搜索 status,先看这个类。排查问题的时候也一样,入口变少了,脑子少炸一半。
面向对象里第二个容易被滥用的是继承。
有些代码一写继承就开始套娃,BaseService、AbstractBaseService、CommonBaseService,看着很有架构感,实际上点进去全是空方法和历史包袱。
我更愿意在规则稳定的时候用继承。
比如系统里有好几种通知方式:短信、邮件、站内信。它们都能发送,但发送细节不一样。
from abc import ABC, abstractmethod
classNoticeSender(ABC):
@abstractmethod
defsend(self, user_id, content):
pass
classSmsSender(NoticeSender):
defsend(self, user_id, content):
print(f"[sms] user={user_id}, msg={content[:20]}")
classMailSender(NoticeSender):
defsend(self, user_id, content):
print(f"[mail] user={user_id}, subject=订单通知")
classInboxSender(NoticeSender):
defsend(self, user_id, content):
print(f"[inbox] user={user_id}, saved=1")
这里的重点不是 ABC 多高级。
重点是后面的业务代码不用关心你到底是短信还是邮件:
defpush_order_notice(sender: NoticeSender, user_id, order_id):
content = f"订单 {order_id} 状态已更新"
sender.send(user_id, content)
senders = [
SmsSender(),
MailSender(),
InboxSender()
]
for sender in senders:
push_order_notice(sender, 18, "202406120091")
这就是多态。
同一个 send 方法,不同对象有不同实现。业务流程只认“能发送通知的东西”,不关心背后是短信网关、邮件服务,还是数据库插一条站内信。
这地方很适合扩展。
以后要加企业微信通知,不用改 push_order_notice,新写一个类就行:
classWeComSender(NoticeSender):
defsend(self, user_id, content):
print(f"[wecom] user={user_id}, pushed=true")
这才是面向对象舒服的地方:不是为了少写几行代码,而是为了少改老代码。
不过 Python 写 OOP,有个坑我得多说一句。
别什么都上类。
一个简单的数据清洗脚本,读文件、过滤字段、写结果,几个函数就能搞定,硬写 5 个类,反而碍事。Python 不是 Java,没必要每件事都先 class XxxManager。
我一般这么判断:
如果只是临时处理数据,用函数。
如果数据和规则总是一起出现,用类。
如果多个对象行为一样但实现不同,再考虑抽象和多态。
比如订单这种,有字段,有状态,有行为,还要防止外面乱改,就适合类。通知发送这种,有统一动作,但渠道不同,就适合多态。至于继承,能不用就先别用,组合很多时候更清楚。
Python 的 OOPS 不是把代码写得“像面向对象”,而是让代码里的业务对象站得住。
订单就该有订单的规矩。
通知就该有通知的接口。
别让字典满天飞,别让状态到处改。出问题的时候你会发现,真正难查的不是语法,而是这条业务规则到底散落在哪十几个文件里。