1. 你在项目中用到了哪些设计模式?能举几个例子吗?
考察点:设计模式应用
参考答案:
项目里用到的主要设计模式有:
1. 策略模式(NodeExecutor),不同类型节点有不同的执行逻辑,通过接口多态实现:
public interface NodeExecutor {
NodeTypeEnum getNodeType();
NodeRunResult execute(NodeState state);
}
// LLMNodeExecutor, PluginNodeExecutor, StartNodeExecutor...
2. 模板方法模式(AbstractNodeExecutor),父类定义执行骨架,子类实现具体逻辑:
public abstract class AbstractNodeExecutor {
public NodeRunResult execute() {
onNodeStart();
resolveInputs();
NodeRunResult result = executeNode(); // 子类实现
storeOutputs();
return result;
}
}
3. 工厂模式(ChatModel 构建),根据配置动态创建不同的模型客户端:
public ChatModel buildChatModel(ModelConfig config) {
switch (config.getProvider()) {
case "openai": return new OpenAiChatModel(...);
case "zhipu": return new ZhiPuChatModel(...);
}
}
4. 观察者模式(回调机制),节点执行状态变化通过回调通知:
callback.onNodeStart(nodeId, nodeName);
callback.onNodeProcess(token);
callback.onNodeEnd(nodeId, result);
参考答案版本 2:
第一个是策略模式。工作流里有很多种节点——LLM 节点、Plugin 节点、Knowledge 节点、条件分支节点,每种节点的执行逻辑完全不同。我们用策略模式来处理:
// 抽象执行器
public abstract class AbstractNodeExecutor {
public abstract NodeTypeEnum getNodeType();
protected abstract NodeRunResult executeNode(NodeState state, Map inputs);
}
// 具体策略:LLM节点
public class LLMNodeExecutor extends AbstractNodeExecutor {
public NodeTypeEnum getNodeType() { return NodeTypeEnum.LLM; }
protected NodeRunResult executeNode(...) { /* 调用大模型 */ }
}
// 具体策略:Plugin节点
public class PluginNodeExecutor extends AbstractNodeExecutor {
public NodeTypeEnum getNodeType() { return NodeTypeEnum.PLUGIN; }
protected NodeRunResult executeNode(...) { /* 调用外部工具 */ }
}
工作流引擎根据节点类型选择对应的执行器,新增节点类型只需要加一个执行器类,不用改引擎核心代码。
第二个是工厂模式。MCP 插件的创建用了工厂(Java 版第二期会实现):调用方不需要知道 MCP 插件怎么创建、怎么连接 Server、怎么解析工具列表,只管调 factory.gen() 就能拿到可用的插件列表。
class McpPluginFactory(BaseModel):
mcp_server_ids: list
mcp_server_urls: list
async def gen(self, span: Span) -> list[McpPlugin]:
# 根据配置创建一系列 MCP 插件
return await self.build_tools(span)
第三个是模板方法模式。Java 版工作流引擎里,所有节点执行器都继承 AbstractNodeExecutor,它定义了执行的骨架。流程是固定的(解析参数、存储变量池),但具体执行逻辑由子类实现。新增节点类型只需要继承 AbstractNodeExecutor、实现 execute 方法。
public abstract class AbstractNodeExecutor {
public NodeRunResult execute() {
onNodeStart();
resolveInputs();
NodeRunResult result = executeNode(); // 子类实现
storeOutputs();
return result;
}
}
第四个是装饰器模式。分布式锁和限流都是用 AOP 注解实现的,核心业务逻辑不用管加锁、限流这些横切关注点。
@DistributedLock(key = "'model_status_sync'", waitTime = 0)
public void syncModelStatus() {
// 业务逻辑
}
@RateLimit(limit = 10, window = 60, dimension = RateLimitDimension.USER)
public Response createWorkflow() {
// 业务逻辑
}
2. NodeExecutor 接口的多态实现体现了什么设计原则?
考察点:开闭原则、策略模式
参考答案:
主要体现了开闭原则(OCP)和依赖倒置原则(DIP)。开闭原则:对扩展开放,对修改关闭。新增节点类型只需要新增一个 Executor 实现类,不需要修改 WorkflowEngine 的代码。
// 新增一个节点类型,只需要新增这一个类
@Component
public class CodeNodeExecutor extends AbstractNodeExecutor {
@Override
public NodeTypeEnum getNodeType() {
return NodeTypeEnum.CODE;
}
@Override
protected NodeRunResult executeNode(NodeState state, Map inputs) {
// 执行代码节点的逻辑
}
}
依赖倒置原则:高层模块不依赖低层模块,都依赖抽象。
// WorkflowEngine 依赖接口,不依赖具体实现
public class WorkflowEngine {
private final Map executors;
// 通过 Spring 自动注入所有实现
public WorkflowEngine(List executors) {
for (NodeExecutor executor : executors) {
this.executors.put(executor.getNodeType(), executor);
}
}
}
参考答案版本 2:
最核心的是开闭原则,"对扩展开放,对修改关闭"——新增节点类型时,只需要加一个新的 Executor 类,不用改工作流引擎的核心代码。
// 引擎核心代码不用改
public class WorkflowEngine {
private Map executors;
public NodeRunResult executeNode(Node node) {
NodeExecutor executor = executors.get(node.getType());
return executor.execute(node); // 多态调用
}
}
// 新增节点?加个类就行
public class NewNodeExecutor extends AbstractNodeExecutor {
public NodeTypeEnum getNodeType() { return NodeTypeEnum.NEW_TYPE; }
protected NodeRunResult executeNode(...) { /* 新逻辑 */ }
}
如果不用多态,引擎里会有一堆 if-else:
// 反面教材
if (node.getType() == LLM) { ... }
else if (node.getType() == PLUGIN) { ... }
else if (node.getType() == KNOWLEDGE) { ... }
// 每加一种节点就要改这里,违反开闭原则
第二...
真诚点赞 诚不我欺
回复