今天翻到一篇不错的技术分享,看完之后自己也琢磨了一下,把思路梳理记录下来。
🎬 个人主页:艾莉丝努力练剑
❄专栏传送门:《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录》
《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》
⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平
🎬 艾莉丝的简介:
文章目录
1.2 C++ 继承、多态与抽象类设计 1.3 底层设计模式:策略模式 1.4 LLMProvider 抽象基类完整头文件达成1.5 派生达成类干活逻辑(例如 DeepSeekProvider)1.6 整体架构分层总览1.7 架构优势总结1.8 面试 / 考试易错考点 结尾1 ~> LLMProvider
基于 C++ 策略模式实现多大模型统一接入 SDK。
1.1 业务背景与需求分析
1.1.1 业务场景
需要接入多款大模型(DeepSeek、ChatGPT、Gemini、Ollama 本地模型等),对外提供统一 SDK,上层业务不需要感知底层模型厂商 API 差异。 前期已搞定公共数据结构、日志库封装,现在搞定模型接入层架构设计。
1.1.2 模型调用原始操作步骤
1. 获取模型厂商 API‑Key
2. 阅读厂商 HTTP API 文档
3. 程序内部发起 HTTP 请求调用模型接口
1.1.3 Provider 必须实现的能力集合
每一个模型接入实现类,都必须具备以下功能:
- 模型初始化:加载配置(apikey、endpoint 等)
- 初始化状态检测:判断模型是否配置合法、可用
- 非流式消息发送:
stream=false,全量一次性返回完整应答 - 流式消息发送:
stream=true,增量分片返回,流式回调输出 - 获取模型名称
- 获取模型描述信息
使用场景:前端页面展示可用模型列表,展示每个模型名称、简介、能力标签。
1.1.4 共性与差异分析
- 公共部分:初始化、可用性检测、发送消息接口定义、持有 apikey/endpoint/ 可用状态标记
- 差异部分
- 模型名称、描述文本不同
- HTTP 请求体参数结构不同
- HTTP 响应 JSON 解析逻辑不同
1.2 C++ 继承、多态与抽象类设计
1.2.1 麻烦:直接硬编码各个模型类的弊端
如果每个模型独立写一套完整类,会冒出来大量重复代码;业务层调用需要大量if‑else分支判断模型类型:
// 糟糕的if‑else写法
if(modelName == "deepseek")
{
DeepSeekProvider dp;
dp.sendMessage(...);
}
else if(modelName == "chatgpt")
{
ChatGPTProvider gp;
gp.sendMessage(...);
}
// 新增模型必须修改此处业务判断代码,违反开闭原则
1.2.2 解决方案:抽取顶层抽象基类 LLMProvider
1. 将所有模型公共接口抽至顶层基类;
2. 将接口声明为纯虚函数,virtual 函数签名 = 0;,使LLMProvider成为抽象类。
3. 抽象类禁止实例化:基类不绑定任何具体大模型,实例化无业务意义;只定义接口规范,由子类搞定全部业务实现。
4. 各个厂商模型实现类继承该抽象基类,重写全部纯虚函数。
继承关系:
基类:LLMProvider(抽象类 / 接口类,只定义契约)派生实现类:DeepSeekProvider / ChatGPTProvider / GeminiProvider / OllamaDeepSeekProvider
1.2.3 C++ 多态调用
基类引用 / 基类指针可以指向派生类对象。业务函数只依赖抽象基类,运行时传入不同子类对象,自动调用对应子类实现,消除大量 if‑else。
// 业务层统一发送消息函数,只依赖抽象基类
void SendMessage(LLMProvider& provider)
{
// 根据传入对象的实际类型,自动调用子类重写的sendMessage
provider.sendMessage(...);
}
1.3 底层设计模式:策略模式
1.3.1 策略模式核心定义
定义一系列算法 / 行为,将每一套行为封装为独立类,各个实现类可以互相替换;通过抽象接口隔离调用方与具体实现,运行时动态选择策略,消除 if‑else 分支,符合开闭原则。
- 抽象策略:抽象基类,定义统一接口
- 具体策略:各个派生子类,实现具体逻辑
- 上下文:业务调用方,只操作抽象策略,不感知底层实现
1.3.2 通俗示例代码(出行策略)
#include
// 抽象策略
class TransportStrategy{
public:
virtual void go() = 0;
};
// 具体策略:走路
class WalkStrategy : public TransportStrategy{
public:
void go() override{
std::cout 关键修正点说明
> 增加虚析构函数virtual ~LLMProvider() = default;,多态继承场景必不可少,防止子类析构不执行内存泄漏;将成员变量修改为protected,子类可以直接访问,原文档写 private 子类无法读取配置;修正原文档多处拼写笔误 initodel → initModel、getodelDesc → getModelDesc;回调std::function参数 const 引用,避免不必要拷贝;补充头文件保护宏#ifndef / #define / #endif。
### 1.5 派生实现类工作逻辑(例如 DeepSeekProvider)
DeepSeekProvider : public LLMProvider
├─ initModel:从map读取apikey、endpoint,赋值给继承而来的m_apiKey/m_endpoint,设置m_isAvailable状态
├─ isAvailable:返回 m_isAvailable
├─ getModelName:返回固定字符串 "deepseek‑chat"
├─ getModelDesc:返回该模型的描述文本
├─ sendMessage:组装DeepSeek HTTP请求体,发起HTTP调用,解析完整JSON响应
└─ sendMessageStream:组装请求开启stream,读取SSE流式响应,分片调用callback回调
### 1.6 整体架构分层总览
┌─────────────── LLMManager(管理器)───────────────┐
│ 业务层只操作抽象基类LLMProvider,不感知具体模型 │
└───────────────────────┬──────────────────────────┘
↓
┌────────── LLMProvider(抽象基类) ──────────┐
│ 纯虚接口定义:initModel / sendMessage / ...│
└───┬───────────────┬───────────────┬──────┘
↓ ↓ ↓
DeepSeekProvider ChatGPTProvider GeminiProvider
实现厂商HTTP交互细节,封装API差异
```
1.7 架构优势总结
1. 消除大量 if‑else:依靠 C++ 多态策略模式,运行时动态切换模型实现。
2. 开闭原则:新增大模型,只需要新增继承LLMProvider的派生类,原有上层代码不需要修改。
3. 统一 SDK 对外接口:第三方使用者只需要依赖抽象接口,不需要关心每家模型 HTTP 细节。
4. 接口强制约束:抽象类纯虚函数强制派生类必须实现全部规定能力,不会冒出来遗漏功能。
5. 兼容云端 API 模型、本地 Ollama 部署模型,同一套框架复用。
1.8 面试 / 考试易错考点
1. 抽象类含有纯虚函数,不能实例化对象;
2. 多态继承必须写虚析构函数,否则 delete 基类指针时子类析构不会执行;
3. 纯虚函数语法:virtual 返回类型 函数名(参数) = 0;;
4. 策略模式核心:封装可变行为,运行时替换,隔离调用方与具体实现;
5. protected 访问限定符:基类成员希望子类可以访问、外部不能访问时使用,不能设置为 private。
结尾
uu们,这篇文章的内容到这里就全部完成了,艾莉丝在这里再次
艾莉丝努力练剑
C/C++ & Linux 底层探索者 | 一个正在努力练剑的技术博主
👀 【关注】 跟随我一起深耕技术领域,见证每一次成长。
❤️
【点赞】 让优质内容被更多人看见,让知识传递更有力量。
⭐
【收藏】 把核心知识点存好,在需要时随时查、随时用。
💬
【评论】 分享你的经验或疑问,评论区一起交流避坑!
不要忘记给博主“一键四连”哦!
“今日练剑达成!”
“技术之路难免有困惑,但同行的人会让前进更有方向。”
结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主“一键四连”哦!
往期回顾:
🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡
>
૮₍ ˶ ˊ ᴥ ˋ˶₎ა
以上就是这次整理的全部内容,希望对你有所启发。如果有不同见解,欢迎在评论区交流讨论。
评论 (0)
暂无评论