2、分层架构设计原则:单一职责、开闭原则、依赖倒置,如何指导业务逻辑分层
好,咱们接着聊。上一章我讲了为什么要分层,这一章咱们来点硬核的——三大设计原则到底怎么指导我们切分业务逻辑。
说实话,我见过太多人把分层搞成了「为了分层而分层」。代码倒是分了四五个包,结果改一个需求,从数据层改到表现层,全得动。这其实就是原则没吃透。
今天我就拿这三个原则,一个个拆开揉碎了讲。你想想看,它们到底在告诉我们什么?
2.1 单一职责原则:别让一个类干太多活
单一职责,英文叫 Single Responsibility Principle,简称 SRP。说白了就是:一个类,只负责一件事。
我在项目中遇到过最典型的反例:一个叫 PlayerService 的类,里面既有玩家登录逻辑,又有背包操作,还有好友系统。你猜怎么着?每次改背包,都得把整个类重新测试一遍。因为改动一个地方,可能影响其他功能。
核心要点:每个模块只对一类变化负责。
那怎么用 SRP 指导分层呢?我习惯这样划分:
- 表现层:只负责协议解析、数据校验、响应组装。不碰业务逻辑。
- 业务逻辑层:只负责核心玩法、规则判断。不碰数据库。
- 数据访问层:只负责 CRUD。不碰业务判断。
举个例子,玩家购买道具:
# 表现层 - 只做协议处理
class PlayerHandler:
def buy_item(self, request):
player_id = request.get('player_id')
item_id = request.get('item_id')
# 调用业务层,不写任何逻辑
result = self.buy_service.buy_item(player_id, item_id)
return self.build_response(result)
# 业务层 - 只做业务判断
class BuyService:
def buy_item(self, player_id, item_id):
player = self.player_repo.get_by_id(player_id)
item = self.item_repo.get_by_id(item_id)
if player.gold < item.price:
return {'code': 1001, 'msg': '金币不足'}
player.gold -= item.price
player.add_item(item)
self.player_repo.save(player)
return {'code': 0, 'msg': '购买成功'}
# 数据层 - 只做数据存取
class PlayerRepository:
def get_by_id(self, player_id):
# 只做数据库查询
pass
def save(self, player):
# 只做数据库更新
pass
你看,每个类只干一件事。改协议格式,只动表现层;改购买规则,只动业务层;换数据库,只动数据层。互不干扰。
小技巧:如果你发现一个类里出现了「并且」「同时」「另外」这些词,说明它可能违反了 SRP。赶紧拆。
2.2 开闭原则:对扩展开放,对修改关闭
开闭原则,Open-Closed Principle,OCP。这句话听起来有点绕,我换个说法:加新功能,别改老代码。
为什么会这样?因为老代码已经测试过了,线上跑得好好的。你一改,可能引入新 bug。我见过最惨的一次,就是改了一个老接口,结果影响了十几个调用方,线上回滚了三次。
那怎么在分层中实现 OCP?我建议用策略模式和接口抽象。
举个例子,游戏里各种活动奖励计算。一开始只有「登录奖励」,后来加了「充值奖励」「分享奖励」。如果每次加活动都改 RewardService,那这个类会越来越臃肿。
# 抽象接口 - 对扩展开放
class RewardStrategy:
def calculate(self, player):
raise NotImplementedError
# 具体实现 - 每个活动一个类
class LoginReward(RewardStrategy):
def calculate(self, player):
return {'gold': 100, 'exp': 50}
class RechargeReward(RewardStrategy):
def calculate(self, player):
return {'gold': player.recharge_amount * 10}
# 业务层 - 不修改
class RewardService:
def __init__(self):
self.strategies = {}
def register_strategy(self, activity_type, strategy):
self.strategies[activity_type] = strategy
def get_reward(self, player, activity_type):
strategy = self.strategies.get(activity_type)
if not strategy:
return {}
return strategy.calculate(player)
你看,加新活动时,只需要新增一个类,注册进去。老代码一行都不用改。这就是 OCP 的魅力。
避坑指南:我曾经犯过一个错误——为了追求「绝对的开闭」,过度抽象。结果接口满天飞,代码可读性极差。记住:OCP 不是让你把所有东西都抽象,而是对可能变化的部分做抽象。
2.3 依赖倒置原则:依赖抽象,不依赖具体
依赖倒置,Dependency Inversion Principle,DIP。这句话的核心是:高层模块不应该依赖低层模块,两者都应该依赖抽象。
嗯,这里要注意。很多新手会误解:以为 DIP 就是「面向接口编程」。其实不止。它强调的是控制反转——谁控制依赖的方向。
我举个例子你就明白了。传统写法:
# 业务层直接依赖具体的数据层实现
class PlayerService:
def __init__(self):
# 直接 new 了一个 MySQL 实现
self.repo = MySQLPlayerRepository()
这有什么问题?如果哪天你要把 MySQL 换成 Redis,或者换成 MongoDB,你得改 PlayerService 的代码。违反了 OCP。
用 DIP 改造后:
# 定义抽象接口
class PlayerRepository:
def get_by_id(self, player_id): pass
def save(self, player): pass
# 业务层依赖抽象
class PlayerService:
def __init__(self, repo: PlayerRepository):
self.repo = repo # 依赖注入
# 具体实现
class MySQLPlayerRepository(PlayerRepository):
def get_by_id(self, player_id):
# MySQL 实现
pass
class RedisPlayerRepository(PlayerRepository):
def get_by_id(self, player_id):
# Redis 实现
pass
现在,PlayerService 不再依赖具体实现,而是依赖抽象接口。换数据库时,只需要换注入的对象。业务层代码完全不用动。
核心要点:依赖倒置让分层架构真正「解耦」。上层不关心下层怎么实现,下层也不关心上层怎么用。大家只认接口。
2.4 三个原则如何协同指导分层
好,三个原则都讲完了。你可能想问:它们之间是什么关系?
我个人的理解是这样的:
- SRP 告诉你:每个层该放什么,不该放什么。这是「分」的依据。
- OCP 告诉你:层与层之间怎么扩展,不破坏现有结构。这是「合」的边界。
- DIP 告诉你:层与层之间怎么依赖,才能松耦合。这是「连」的方式。
三者缺一不可。没有 SRP,分层会混乱;没有 OCP,扩展会痛苦;没有 DIP,改一个层会牵连其他层。
我画个简单的对照表,方便你理解:
| 原则 | 解决的问题 | 在分层中的体现 |
|---|---|---|
| 单一职责 | 一个类职责过多 | 表现层只做协议,业务层只做规则,数据层只做存取 |
| 开闭原则 | 加功能需要改老代码 | 用策略模式、接口抽象,新增功能不修改已有代码 |
| 依赖倒置 | 高层依赖低层,耦合紧密 | 依赖注入,面向接口编程,控制反转 |
我的建议:刚开始做分层时,别追求一步到位。先按 SRP 把代码拆开,再逐步引入 DIP 和 OCP。我见过太多人一上来就搞依赖注入框架,结果代码反而更复杂了。循序渐进,才是正道。
好,这一章就到这里。下一章我会讲具体的分层实践——从项目结构到代码组织,手把手带你搭一个可维护的游戏服务器。