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。我见过太多人一上来就搞依赖注入框架,结果代码反而更复杂了。循序渐进,才是正道。

好,这一章就到这里。下一章我会讲具体的分层实践——从项目结构到代码组织,手把手带你搭一个可维护的游戏服务器。