策略模式与分布式软总线连接模块
2026/7/18 4:45:03 网站建设 项目流程

概述:策略模式是连接模块的骨架

策略模式(Strategy Pattern)是分布式软总线连接模块的第一架构模式——它决定了整个模块的骨架结构。简单来说,策略模式解决的核心问题是:同一件事有多种做法,需要在运行时灵活选择用哪种做法

在连接模块中,"连接设备"这件事有三种做法——通过蓝牙BR、通过蓝牙BLE、通过TCP,将来还可能新增WiFi Direct等。策略模式让连接管理器不需要知道每种连接具体怎么实现,只需要定义一套统一的"接口契约",然后让各连接类型各自实现。调用时,根据连接类型自动路由到正确的实现。

这就好比你去餐厅点餐:菜单(ConnectFuncInterface)定义了"炒菜"这个动作,但后厨有川菜师傅(BR)、粤菜师傅(BLE)、鲁菜师傅(TCP),你点什么口味,就派给对应的师傅来做——你不需要亲自跑到后厨去指定。


一、策略模式原理详解

1.1 模式定义

策略模式(Strategy Pattern),又称政策模式(Policy Pattern),属于GoF 23种设计模式中的行为型模式

核心思想:定义一组算法(策略),将每一个算法封装到具有共同接口的独立类中,使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户。

用大白话说就是:把"做什么"和"怎么做"分开。"做什么"由上下文(Context)决定,"怎么做"由具体策略(Strategy)决定,两者通过接口解耦。

1.2 三个核心角色

角色英文名中文解释职责
策略接口Strategy(策略/抽象策略)定义所有支持的算法的公共接口声明统一的方法签名,是所有具体策略的契约
具体策略ConcreteStrategy(具体策略)实现策略接口中定义的算法每个具体策略封装一种具体的算法实现
上下文Context(环境/上下文)持有策略引用,将请求委托给策略维护对策略对象的引用,在运行时切换策略

1.3 经典UML结构

1.4 经典代码骨架(伪代码)

// 1. 策略接口:定义统一契约 typedef struct { int32_t (*algorithm)(int32_t input); // 算法接口 } Strategy; // 2. 具体策略A:算法A的实现 int32_t AlgorithmA(int32_t input) { return input * 2; } Strategy strategyA = { .algorithm = AlgorithmA }; // 3. 具体策略B:算法B的实现 int32_t AlgorithmB(int32_t input) { return input + 10; } Strategy strategyB = { .algorithm = AlgorithmB }; // 4. 上下文:持有策略引用,运行时选择 Strategy *currentStrategy; void setStrategy(Strategy *s) { currentStrategy = s; } int32_t doSomething(int32_t input) { return currentStrategy->algorithm(input); } // 5. 使用 setStrategy(&strategyA); // 选择策略A result = doSomething(5); // 调用 → 5 * 2 = 10 setStrategy(&strategyB); // 切换到策略B result = doSomething(5); // 调用 → 5 + 10 = 15

1.5 策略模式的核心价值

价值点说明
消除条件分支不用写一堆if-elseswitch-case,用策略替换直接分发
算法独立变化新增算法不影响已有算法和上下文
运行时切换可以在程序运行过程中动态更换策略
复用与组合同一策略可被多个上下文复用

二、连接模块中策略模式的完整实现

2.1 角色映射:三个角色如何对应

经典角色连接模块对应具体代码
Strategy(策略接口)ConnectFuncInterfacesoftbus_conn_manager.h
ConcreteStrategy(具体策略)BR策略、BLE策略、TCP策略ConnInitBr()ConnInitBle()ConnInitTcp()返回的对象
Context(上下文)ConnManager(连接管理器)g_connManager[]数组 + 路由分发函数

2.2 策略接口:ConnectFuncInterface

这是整个策略模式的灵魂——它定义了"连接"这件事到底包含哪些动作。所有连接类型必须实现这套接口:

typedef struct { int32_t (*ConnectDevice)(const ConnectOption *option, uint32_t requestId, const ConnectResult *result); int32_t (*PostBytes)(uint32_t connectionId, uint8_t *data, uint32_t len, ...); int32_t (*DisconnectDevice)(uint32_t connectionId); int32_t (*DisconnectDeviceNow)(const ConnectOption *option); int32_t (*GetConnectionInfo)(uint32_t connectionId, ConnectionInfo *info); int32_t (*StartLocalListening)(const LocalListenerInfo *info); int32_t (*StopLocalListening)(const LocalListenerInfo *info); bool (*CheckActiveConnection)(const ConnectOption *info, bool needOccupy); int32_t (*UpdateConnection)(uint32_t connectionId, UpdateOption *option); int32_t (*PreventConnection)(const ConnectOption *option, uint32_t time); int32_t (*ConfigPostLimit)(const LimitConfiguration *configuration); } ConnectFuncInterface;

用大白话翻译每个方法:

方法含义生活类比
ConnectDevice建立连接拨打电话
PostBytes发送数据说话
DisconnectDevice断开连接(优雅)说再见后挂电话
DisconnectDeviceNow立即断开连接直接挂电话
GetConnectionInfo查询连接信息查看通话记录
StartLocalListening开始监听打开电话等待来电
StopLocalListening停止监听关闭电话不再接听
CheckActiveConnection检查是否有活跃连接查看是否在通话中
UpdateConnection更新连接参数通话中调整音量
PreventConnection阻止连接设置免打扰
ConfigPostLimit配置流量限制设置流量套餐

2.3 具体策略:三种连接实现

BR策略(蓝牙经典连接)

在 softbus_conn_br_manager.c:1856 中填充:

static ConnectFuncInterface connectFuncInterface = { .ConnectDevice = BrConnectDevice, // BR方式建立连接 .PostBytes = ConnBrPostBytes, // BR方式发送数据 .DisconnectDevice = BrDisconnectDevice, // BR方式优雅断连 .DisconnectDeviceNow = BrDisconnectDeviceNow, // BR方式立即断连 .GetConnectionInfo = BrGetConnectionInfo, // BR方式查询信息 .StartLocalListening = BrStartLocalListening, // BR方式开始监听 .StopLocalListening = BrStopLocalListening, // BR方式停止监听 .CheckActiveConnection = BrCheckActiveConnection, // BR方式检查活跃连接 .UpdateConnection = NULL, // BR不支持更新 .PreventConnection = BrPendConnection, // BR支持阻止连接 .ConfigPostLimit = ConnBrTransConfigPostLimit, // BR流量控制 };
BLE策略(蓝牙低功耗连接)

在 softbus_conn_ble_manager.c:2360 中填充:

static ConnectFuncInterface bleFuncInterface = { .ConnectDevice = BleConnectDevice, .PostBytes = ConnBlePostBytes, .DisconnectDevice = BleDisconnectDevice, .DisconnectDeviceNow = BleDisconnectDeviceNow, .GetConnectionInfo = BleGetConnectionInfo, .StartLocalListening = BleStartLocalListening, .StopLocalListening = BleStopLocalListening, .CheckActiveConnection = BleCheckActiveConnection, .UpdateConnection = BleUpdateConnection, // BLE支持更新(如调整优先级) .PreventConnection = NULL, // BLE不支持阻止连接 .ConfigPostLimit = ConnBleTransConfigPostLimit, };
TCP策略(WiFi局域网连接)

在 softbus_tcp_connect_manager.c:767 中填充:

static void InitTcpInterface(void) { g_tcpInterface.ConnectDevice = TcpConnectDevice; g_tcpInterface.DisconnectDevice = TcpDisconnectDevice; g_tcpInterface.DisconnectDeviceNow = TcpDisconnectDeviceNow; g_tcpInterface.PostBytes = TcpPostBytes; g_tcpInterface.GetConnectionInfo = TcpGetConnectionInfo; g_tcpInterface.StartLocalListening = TcpStartListening; g_tcpInterface.StopLocalListening = TcpStopListening; g_tcpInterface.CheckActiveConnection = TcpCheckActiveConnection; g_tcpInterface.UpdateConnection = NULL; // TCP不支持更新 g_tcpInterface.PreventConnection = NULL; // TCP不支持阻止连接 }

关键观察:三种策略对同一接口方法的实现完全不同,而且有些方法设为NULL(表示该连接类型不支持此操作)。这正是策略模式的精髓——统一接口,差异实现

2.4 上下文:连接管理器的策略注册与路由

2.4.1 策略注册(初始化时绑定具体策略)
ConnectFuncInterface *g_connManager[CONNECT_TYPE_MAX] = { 0 }; // 策略数组 int32_t ConnServerInit(void) { // ... 前置初始化 ... // 将三种具体策略注册到策略数组中 if (isSupportTcp) { g_connManager[CONNECT_TCP] = ConnInitTcp(&g_connManagerCb); // TCP策略 } g_connManager[CONNECT_BR] = ConnInitBr(&g_connManagerCb); // BR策略 g_connManager[CONNECT_BLE] = ConnInitBle(&g_connManagerCb); // BLE策略 }

这就是策略模式的注册阶段——把"川菜师傅""粤菜师傅""鲁菜师傅"安排到后厨的对应工位上。

2.4.2 策略路由(运行时分发到正确策略)

连接管理器通过connectionId的高16位来识别连接类型,从而路由到正确的策略:

#define CONNECT_TYPE_SHIFT 16 // connectionId = (ConnectType << 16) | 序号 // 例如:BR连接ID = (2 << 16) | 5 = 0x00020005

路由分发示例——连接设备

int32_t ConnConnectDevice(const ConnectOption *info, uint32_t requestId, const ConnectResult *result) { // 1. 类型校验 if (ConnTypeCheck(info->type) != SOFTBUS_OK) { return ERROR; } // 2. 策略路由:根据类型直接索引到对应策略,调用其ConnectDevice方法 return g_connManager[info->type]->ConnectDevice(info, requestId, result); }

路由分发示例——发送数据

int32_t ConnPostBytes(uint32_t connectionId, ConnPostData *data) { // 1. 从connectionId提取连接类型 ConnectType type = (connectionId >> CONNECT_TYPE_SHIFT); // 2. 策略路由:直接索引到对应策略 return g_connManager[type]->PostBytes(connectionId,>int32_t ConnDisconnectDevice(uint32_t connectionId) { ConnectType type = (connectionId >> CONNECT_TYPE_SHIFT); return g_connManager[type]->DisconnectDevice(connectionId); }

2.5 完整的策略模式流转图

2.6 与经典策略模式的差异

连接模块的实现与教科书上的策略模式有一些务实的差异,这些差异体现了嵌入式系统的工程智慧:

差异点经典策略模式连接模块实现原因
策略数量通常2-3个,运行时切换3个固定策略,初始化时注册连接类型在系统生命周期内不变
策略选择由Context决定用哪个由调用方(ConnectType)或连接ID决定调用方明确知道要用哪种连接
策略存储单个策略指针策略数组g_connManager[MAX]需要同时管理多种策略
接口实现所有方法必须实现允许NULL(不支持的操作)不是每种连接都支持所有操作
语言机制C++虚函数/接口继承C语言函数指针结构体系统级服务要求C语言、高性能

三、为什么要使用策略模式?

3.1 如果不用策略模式会怎样?

让我们想象一下没有策略模式的代码会是什么样:

// ❌ 反面教材:不用策略模式的实现 int32_t ConnConnectDevice(const ConnectOption *info, ...) { if (info->type == CONNECT_TCP) { // TCP连接的100行逻辑... socket = socket(AF_INET, SOCK_STREAM, 0); connect(socket, ...); // ... } else if (info->type == CONNECT_BR) { // BR连接的200行逻辑... SppConnect(info->brOption.brMac); // 状态机处理... // ACL碰撞处理... // ... } else if (info->type == CONNECT_BLE) { // BLE连接的300行逻辑... GattConnect(info->bleOption.bleMac); // 服务发现... // MTU协商... // 特征订阅... // ... } } int32_t ConnPostBytes(uint32_t connectionId, ...) { if (type == CONNECT_TCP) { /* TCP发送逻辑 */ } else if (type == CONNECT_BR) { /* BR发送逻辑 */ } else if (type == CONNECT_BLE) { /* BLE发送逻辑 */ } } // 每个函数都要重复同样的 if-else 分支!

问题一目了然

问题严重程度说明
代码膨胀🔴 严重每个函数都塞满所有连接类型的逻辑,一个文件可能上万行
修改扩散🔴 严重修改BR连接逻辑,要在一个巨大函数中找到对应分支,容易改错
新增困难🔴 严重新增WiFi Direct连接,要修改每一个函数的if-else
无法独立测试🟡 中等无法单独测试BR逻辑,必须带着TCP和BLE一起编译
编译依赖🟡 中等所有连接类型的头文件互相纠缠

3.2 使用策略模式后的收益

收益1:消除条件分支(最直观的收益)

之前:每个函数都有if-else分支判断类型之后:一行代码直接路由

// ✅ 策略模式:一行搞定,无需if-else return g_connManager[info->type]->ConnectDevice(info, requestId, result);

这就是策略模式最核心的威力——用多态替代条件分支

收益2:关注点分离(每个策略独立开发)
BR开发工程师 ──→ 只看 br/ 目录,不需要理解BLE和TCP BLE开发工程师 ──→ 只看 ble/ 目录,不需要理解BR和TCP TCP开发工程师 ──→ 只看 tcp/ 目录,不需要理解BR和BLE

每个连接类型是完全独立的编译单元,有自己的:

  • 状态机(ConnBrState/ConnBleState
  • 连接实例(ConnBrConnection/ConnBleConnection
  • 数据传输(ConnBrTrans/ConnBleTrans
  • 发送队列(ConnBrSendQueue/ConnBleSendQueue
收益3:开放封闭原则(OCP)—— 新增连接类型零修改

假设未来要新增一种NFC连接,只需要:

// 第1步:实现NFC策略(新增文件,不修改任何现有文件) static ConnectFuncInterface nfcFuncInterface = { .ConnectDevice = NfcConnectDevice, .PostBytes = NfcPostBytes, .DisconnectDevice = NfcDisconnectDevice, // ... }; // 第2步:在初始化中注册(仅增加一行) g_connManager[CONNECT_NFC] = ConnInitNfc(&g_connManagerCb);

不需要修改

  • ❌ 不改ConnectFuncInterface接口定义
  • ❌ 不改ConnConnectDevice()路由逻辑
  • ❌ 不改 BR/BLE/TCP 的任何代码
  • ❌ 不改上层业务调用代码

这就是对扩展开放,对修改封闭

收益4:接口契约的约束力

ConnectFuncInterface不仅是策略接口,更是契约——它强制要求每种连接类型必须实现核心方法(ConnectDevicePostBytesDisconnectDevice等),否则注册时会设为NULL,调用时会被拦截:

if (g_connManager[type]->PostBytes == NULL) { return SOFTBUS_CONN_MANAGER_OP_NOT_SUPPORT; // 明确告知不支持 }

这比if-else方式更安全——不支持的操作有明确的错误码返回,而不是遗漏分支导致未定义行为。

收益5:运行时能力探测

策略模式天然支持能力探测——通过检查策略接口的函数指针是否为NULL,可以知道某种连接类型支持哪些操作:

操作BRBLETCP
ConnectDevice
PostBytes
DisconnectDevice
UpdateConnection
PreventConnection
ConfigPostLimit

这种差异化能力if-else方式中很难优雅地表达。

3.3 为什么用C函数指针而非C++虚函数?

这是很多人会问的问题。连接模块选择C语言函数指针实现策略模式,有深层的技术考量:

对比维度C++虚函数C函数指针
运行时开销vtable间接寻址(2次指针解引用)直接函数指针调用(1次解引用)
内存布局每个对象额外4/8字节vptr无额外开销
ABI稳定性受编译器影响(vtable布局可能不同)函数指针布局稳定
兼容性需要C++运行时支持纯C,无运行时依赖
内核态可用❌ 内核通常不支持C++✅ 内核广泛使用函数指针
调试可读性虚函数调用栈不够直观函数指针名称可打印

结论:对于分布式软总线这种系统级基础服务,C函数指针是比C++虚函数更合适的选择——更轻量、更稳定、更通用。

3.4 策略模式与观察者模式的协同

连接模块中,策略模式和观察者模式不是孤立存在的,而是协同工作形成完整的架构:

具体协同流程(以BR连接建立为例):

1. 上层调用 ConnConnectDevice(BR类型) → 策略模式路由到 g_connManager[BR]->ConnectDevice() → BrConnectDevice() 执行BR连接逻辑 2. BR连接成功后,调用 g_connManagerCb.OnConnected() → ConnManagerConnected() 触发观察者模式 → 遍历 g_listenerList,通知所有业务模块 → 认证模块.OnConnected() → 启动认证 → 通道模块.OnConnected() → 创建通道 → 代理模块.OnConnected() → 调整策略

策略模式负责"往下走"(选择实现),观察者模式负责"往上走"(通知结果),两者一上一下,构成了完整的控制流和数据流。


总结:策略模式的设计精髓

核心价值一句话

策略模式让连接模块实现了"一套接口、多种实现、按需路由"——上层只管"我要连接",不用管"怎么连接";底层各自实现"怎么连接",不用管"谁在调用"。

设计原则落地总结

设计原则策略模式如何落地
开闭原则(OCP)新增连接类型只需新增策略实现+注册一行,零修改现有代码
单一职责(SRP)每种连接策略只负责自己的连接逻辑,管理器只负责路由
里氏替换(LSP)所有策略都可替换,上层代码不依赖具体策略
接口隔离(ISP)ConnectFuncInterface只包含连接相关方法,不掺杂业务逻辑
依赖倒置(DIP)管理器依赖抽象接口ConnectFuncInterface,不依赖具体BR/BLE/TCP实现

与现实生活的类比

把连接模块想象成一个快递公司

角色快递公司连接模块
策略接口"配送"这个动作ConnectFuncInterface
具体策略陆运/空运/海运三种配送方式TCP/BR/BLE三种连接方式
上下文调度中心根据包裹类型选择配送方式ConnManager根据ConnectType路由
观察者客户收到配送通知业务模块收到连接事件回调

调度中心(上下文)不需要知道陆运怎么开车、空运怎么开飞机,只需要知道"配送"这个动作——这就是策略模式的本质:把"做什么"和"怎么做"彻底分开

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询