抽象 = 忽略次要细节,只保留对外必须的信息;把复杂的内部逻辑藏起来,对外暴露简单的契约。
1. 接口 / 抽象类 -多态抽象
接口多态(插头):用interface语法,实现可替换实现
目的:屏蔽多个不同实现的差异,对外一套调用方式。
TypeScript
// 抽象契约:插头规格
interface Storage {
set(key:string, value:string):Promise<void>
get(key:string):Promise<string|null>
}
// 不同实现,内部逻辑完全不一样,但插头一样
class RedisStorage implements Storage {}
class FileStorage implements Storage {}
class MemoryStorage implements Storage {}
上层业务只调用 storage.set(),不用管存 Redis、文件还是内存。
对应:Runner、MCP 都属于这一类抽象。
2. 函数-封装抽象(最简单、最基础的抽象)
函数抽象:用function语法,实现隐藏细节
把一堆重复的细节代码包进函数,对外只给入参返回值,隐藏内部细节,只是隐藏复杂度。
TypeScript
// 抽象:把fetch、token、异常处理全部藏在函数内部
async function postApi(url, payload){
const res = await fetch(url, {
method:"POST",
headers:{"token":localStorage.getItem("token")},
body:JSON.stringify(payload)
})
if(!res.ok) throw new Error("请求失败")
return res.json()
}
// 业务层只关心我要调用哪个接口,不用管token、http状态码细节
const data = await postApi("/api/xxx", {name:"xxx"})
隐藏内部实现细节,只暴露简单契约。
3. 数据抽象(对象 / 类,对现实概念建模)
目的:把零散的变量,打包成一个概念实体,对现实世界事物做抽象建模。
不做抽象:零散到处传参数,以及赋值
TypeScript
// 一堆散落变量
const orderId = "O123";
const amount = 1000;
const status = "paid";
const createTime = "2026‑08‑17";
数据抽象:把现实概念「用户信息」抽象成对象 / 类
java典型的DO 实体类
java
package com.jd.jdd.gyl.jxd.repay.dao.entity;
import com.jd.jdd.gyl.jxd.repay.domain.enums.LoanTypeEnum;
import lombok.Data;
@Data
public class CompanyUserRelationshipDO implements Serializable {
private static final long serialVersionUID = -5420248199202692599L;
private Integer id;
private String pin;
private String vendorId;
private String companyId;
/**
* 1:老用户,2:新用户
*/
private Integer userStatus;
}
Agent 里的 Memory、Skill 大量是这种抽象:把现实的 "记忆""工作经验" 建模成程序对象。
4、模块抽象(文件 / 模块,边界隔离)
模块抽象:用import/export语法,实现文件级边界隔离
把一组相关能力收敛到一个模块文件,只导出少量对外 API,内部实现全部私有。
TypeScript
// 内部函数,模块私有,外部永远看不见
function parseMcpMessage(raw:string){/*复杂解析逻辑*/}
// 只对外暴露2个方法,这就是模块抽象
export function connectMcp(serverUrl:string){}
export function callMcpTool(toolName:string, args:object){}
5. 泛型抽象:剥离具体类型,适配多种数据
泛型抽象核心:
把「数据类型」也变成参数,代码逻辑固定,不绑定死某一种具体类型,一套逻辑处理多种类型。
✅ 缓存是非常经典的例子,但泛型的使用场景远不止缓存。
java
// T 是类型参数,不再绑定死字符串或其他数据类型;存什么类型由调用方决定
public class Cache<T> {
public void set(String key, T value){}
public T get(String key){ return null; }
}
✅ JSF泛化调用
RPC/HTTP 统一返回体业务接口统一返回包装对象,不知道返回的 data 是什么实体:
(泛型只是编译期类型约束。 运行时 JSON 序列化,泛型信息会被擦除;
但写代码的时候编译器会帮你校验类型,防止你塞错对象进 data。)
java
// =========【1、定义泛型类】泛型模板(写在公共common包,框架层)=========
@Data
public class Result<T> {
private Integer code;
private String msg;
private T data; // data的类型交给调用方指定
}
Result<T> :带<T>模板定义:模板类,不是响应,是图纸。
Result<T>:泛型抽象,剥离 data 的实体类型,一套模板适配全部接口响应
这里的 <T> 是类型占位符,只是一个符号,不知道最终是什么类型,把类型 "留空" 交给使用的人来填。
java
// =========【2、调用/使用泛型,给T传入真实类型(业务Controller,调用方)】=========
Result<CompanyUserRelationshipDO> resp1;
Result<List<Order>> resp2;
Result<CompanyUserRelationshipDO>:这就是实例化泛型, Result<CompanyUserRelationshipDO> resp1:这才是最终接口响应对象(响应实例)
把真实类型 CompanyUserRelationshipDO 赋值给占位符 T。
此时:T = CompanyUserRelationshipDO,
data字段就变成 private CompanyUserRelationshipDO data;
调用方:
而写这行代码的 Controller,就是泛型的调用方;--不确定查回来的是什么类型的数据源
java
// Controller 声明的响应类型,Controller 是泛型的调用方,使用泛型Result模板
@GetMapping("/query/relation")
public Result<CompanyUserRelationshipDO> getRelation(){
CompanyUserRelationshipDO entity = companyUserRelationshipMapper.selectById(123);
// 这里返回的就是接口HTTP响应
return Result.success(entity);
}
响应:HTTP 返回 JSON(最终对外响应报文)
java
{
"code":200,
"msg":"success",
"data": {
"id":123,
"pin":"xxx",
"vendorId":"xxx"
}
}
PS:嵌套泛型 Result<List<Order>>拆解
- 外层 Result 的泛型 T =
List<Order> - List 本身也是泛型,List 的泛型 E =
Order
响应:
java
{
"code":200,
"msg":"success",
"data":[
{ "orderId":"O001" },
{ "orderId":"O002" }
]
}
✅ DAO 层通用基类
Mybatis‑Plus 的 BaseMapper<T>
CRUD 的通用逻辑固定,泛型 T 传入 DO 实体类型,这也是泛型抽象。
java
// BaseMapper内置selectById、insert、delete,T代表数据库实体DO
public interface BaseMapper<T> {
T selectById(Long id);
int insert(T entity);
}
//你的DO直接继承,不用重复写CRUD方法
public interface CompanyUserRelationshipMapper extends BaseMapper<CompanyUserRelationshipDO> {
}
6. 中间件抽象(Web、Agent 框架大量使用)
把处理流程抽象,把主逻辑和附加逻辑做隔离。 比如 Express/Koa、LangChain Callback 中间件。
请求 → 日志中间件 → 鉴权中间件 → 业务主逻辑 → 响应
主业务逻辑完全不需要写日志、鉴权代码;附加能力被抽象抽离。
Agent Runner 内部的回调钩子、日志、超时拦截,就是中间件抽象。
7. 配置驱动抽象
把逻辑变成配置,代码不改动将业务规则抽象为配置文件,不需要修改代码,改配置即可改变行为。
skill.yaml
TypeScript
name:故障排查
max_step: 8
rules:
- 优先查日志
- 失败最多重试2次
代码读取配置驱动执行,业务规则和代码解耦。 Agent 的 Skill 大多就是这种抽象,把业务经验抽象成配置。