云容笔谈·东方红颜影像生成系统Java八股文实践:设计模式在AI服务调用中的应用
云容笔谈·东方红颜影像生成系统Java八股文实践设计模式在AI服务调用中的应用每次面试被问到“设计模式有什么用”是不是总感觉有点心虚背得滚瓜烂熟的单例、工厂、观察者一到实际项目里好像除了让代码看起来“高级”一点就没什么实际用处了。今天咱们就来点不一样的。我们不谈理论直接上手看看在调用一个真实的AI图像生成服务——“云容笔谈·东方红颜影像生成系统”时这些经典的Java八股文知识是怎么让我们的代码从“能跑就行”变成“优雅又好用”的。想象一下你正在开发一个电商平台的营销素材生成后台。运营同学需要为不同的节日、不同的商品线快速生成风格各异的古风人物宣传图。有的要“水墨丹青”的意境有的要“工笔重彩”的精致有的要“二次元国风”的活泼。后端服务需要调用AI模型但每次请求的参数、处理逻辑、甚至回调方式都可能不同。如果一股脑写成一坨if-else代码很快就会变得难以维护和扩展。这就是设计模式登场的时候了。我们通过一个具体的Java服务项目来看看如何用工厂模式来封装不同风格的生成请求用观察者模式来优雅地处理漫长的生成进度通知再用单例模式来确保我们调用AI服务的客户端既高效又安全。你会发现这些“八股文”不再是面试的负担而是解决实际工程问题的利器。1. 项目背景与核心挑战“云容笔谈·东方红颜影像生成系统”是一个提供古风人像生成能力的AI服务。它对外提供HTTP API允许我们传入一段文本描述Prompt选择一种绘画风格如“水墨”、“工笔”、“动漫”并指定一些高级参数如分辨率、生成步数然后返回一个任务ID。由于图像生成比较耗时服务采用的是异步处理模式你先提交任务服务立刻返回一个任务ID然后你需要轮询另一个接口或者等待服务回调来获取最终的生成结果图片。我们的Java后端服务需要集成这个AI能力主要面临三个挑战请求的多样性运营需求多变今天要“七夕鹊桥”主题的水墨风格明天可能要“新春贺岁”主题的工笔风格。每种风格的请求参数结构、校验逻辑、甚至调用的API端点都可能略有不同。硬编码会导致代码臃肿每加一个新风格就要改核心逻辑。响应的异步性一次生成可能耗时10秒到1分钟。用户提交后我们不能让线程干等必须立即返回然后通过其他方式如WebSocket、长轮询将生成进度和最终结果推送给前端。如何优雅地管理这些异步状态和回调客户端的管理调用AI服务的HTTP客户端比如我们用OkHttp或Apache HttpClient需要管理连接池、超时设置、重试机制等。这个客户端实例在整个应用生命周期内通常只需要一个但必须确保线程安全且避免重复创建造成的资源浪费。面对这些挑战我们很自然地想到了三个老朋友工厂模式、观察者模式和单例模式。下面我们就来看看它们是如何各司其职让整个集成过程变得清晰、健壮。2. 用工厂模式封装多变的生成请求当生成需求有多种风格时最差的做法是在业务逻辑里写一堆switch-case或if-else。这不仅违反了开闭原则对扩展开放对修改关闭也让核心业务代码充满了具体的风格细节。工厂模式在这里派上了大用场。它的核心思想是定义一个创建对象的接口但将实际创建过程延迟到子类。对于我们的场景“对象”就是一个个具体的生成请求。2.1 定义请求的抽象与具体实现首先我们定义一个抽象的请求生成器。/** * 抽象的AI图像生成请求构造器 */ public abstract class ImageGenerationRequest { protected String prompt; // 文本描述 protected String style; // 风格名称 public ImageGenerationRequest(String prompt, String style) { this.prompt prompt; this.style style; } /** * 构建最终发送给AI服务的请求参数JSON对象 * 不同风格可能有不同的参数结构 */ public abstract JSONObject buildRequestBody(); /** * 获取对应风格应该调用的API端点 */ public abstract String getApiEndpoint(); /** * 参数校验不同风格可能有不同的校验规则 */ public abstract void validate() throws IllegalArgumentException; }然后我们为每种风格创建具体的实现类。比如“水墨风格”/** * 水墨丹青风格请求 */ public class InkPaintingRequest extends ImageGenerationRequest { private int inkWeight 5; // 墨色浓淡1-10 private boolean includePoetry false; // 是否题诗 public InkPaintingRequest(String prompt, String style, int inkWeight, boolean includePoetry) { super(prompt, style); this.inkWeight inkWeight; this.includePoetry includePoetry; } Override public JSONObject buildRequestBody() { JSONObject body new JSONObject(); body.put(prompt, this.prompt); body.put(style, this.style); // 水墨风格特有参数 JSONObject extraParams new JSONObject(); extraParams.put(ink_weight, this.inkWeight); extraParams.put(render_poetry, this.includePoetry); extraParams.put(background, faint_misty); // 默认朦胧背景 body.put(params, extraParams); return body; } Override public String getApiEndpoint() { // 水墨风格可能调用一个特定的优化接口 return /api/v1/generate/ink; } Override public void validate() { if (inkWeight 1 || inkWeight 10) { throw new IllegalArgumentException(墨色浓淡参数必须在1-10之间); } // 可以添加更多针对水墨风格的校验比如prompt是否包含山水意境词 } }再比如“工笔重彩风格”/** * 工笔重彩风格请求 */ public class FineBrushRequest extends ImageGenerationRequest { private String colorPalette vibrant; // 色彩盘 private int detailLevel 8; // 细节等级 public FineBrushRequest(String prompt, String style, String colorPalette, int detailLevel) { super(prompt, style); this.colorPalette colorPalette; this.detailLevel detailLevel; } Override public JSONObject buildRequestBody() { JSONObject body new JSONObject(); body.put(prompt, this.prompt); body.put(style, this.style); JSONObject extraParams new JSONObject(); extraParams.put(color_palette, this.colorPalette); extraParams.put(detail_level, this.detailLevel); extraParams.put(line_precision, high); // 强调线条精度 body.put(params, extraParams); return body; } Override public String getApiEndpoint() { return /api/v1/generate/fine_brush; } Override public void validate() { if (detailLevel 1 || detailLevel 10) { throw new IllegalArgumentException(细节等级必须在1-10之间); } // 校验色彩盘是否在允许的列表内 } }2.2 实现简单的工厂现在我们需要一个地方来根据“风格”这个字符串创建出对应的请求对象。这就是工厂。/** * 图像生成请求工厂 */ public class GenerationRequestFactory { public static ImageGenerationRequest createRequest(String style, String prompt, MapString, Object styleSpecificParams) { switch (style.toLowerCase()) { case ink: int inkWeight (int) styleSpecificParams.getOrDefault(inkWeight, 5); boolean includePoetry (boolean) styleSpecificParams.getOrDefault(includePoetry, false); return new InkPaintingRequest(prompt, ink_painting, inkWeight, includePoetry); case fine_brush: String palette (String) styleSpecificParams.getOrDefault(colorPalette, vibrant); int detail (int) styleSpecificParams.getOrDefault(detailLevel, 8); return new FineBrushRequest(prompt, fine_brush, palette, detail); case anime: // 可以继续扩展动漫风格... // return new AnimeStyleRequest(...); default: throw new IllegalArgumentException(不支持的风格类型: style); } } }2.3 在业务层中的使用在业务服务里代码变得非常干净和聚焦。Service public class AIImageService { Autowired private AIServiceClient aiClient; // 后面会讲到的单例客户端 public String submitGenerationTask(String prompt, String style, MapString, Object styleParams) { // 1. 使用工厂创建请求对象 ImageGenerationRequest request GenerationRequestFactory.createRequest(style, prompt, styleParams); // 2. 校验参数多态调用执行的是具体风格的校验逻辑 request.validate(); // 3. 构建请求体并获取API端点 JSONObject requestBody request.buildRequestBody(); String endpoint request.getApiEndpoint(); // 4. 调用AI服务 String taskId aiClient.submitTask(endpoint, requestBody); // 5. 触发异步结果监听下一节观察者模式 notifyTaskSubmitted(taskId, style); return taskId; } // ... 其他方法 }这样做的好处解耦业务逻辑AIImageService不再关心具体风格如何构建请求它只和抽象的ImageGenerationRequest接口交互。易扩展当需要新增一种“剪纸风格”时我们只需要新建一个PaperCutRequest类并在工厂的switch里加一个case。业务层的核心代码一行都不用改。职责清晰每种风格的参数校验、请求体构建逻辑都封装在各自的类里符合单一职责原则。3. 用观察者模式处理异步生成进度用户提交任务后生成过程在AI服务端进行。我们的Java服务需要将这个过程的进度如“排队中”、“生成中20%”、“完成”、“失败”实时地通知给前端或者更新数据库状态又或者触发一条钉钉消息通知运营人员。这是一个典型的一对多的依赖关系一个任务状态的变化需要通知到多个关心它的“观察者”。观察者模式完美契合这个场景。3.1 定义观察者与被观察者首先定义观察者接口。任何想监听任务状态变化的对象都需要实现它。/** * 观察者接口任务状态监听器 */ public interface GenerationTaskObserver { /** * 当任务状态更新时被调用 * param taskId 任务ID * param newStatus 新状态如PROCESSING, SUCCESS, FAILED * param progress 进度百分比 (0-100)可能为null * param resultUrl 生成结果的URL可能为null */ void onTaskStatusUpdate(String taskId, String newStatus, Integer progress, String resultUrl); }然后我们实现几个具体的观察者/** * 观察者1WebSocket推送通知前端页面 */ Component public class WebSocketNotifier implements GenerationTaskObserver { Autowired private SimpMessagingTemplate messagingTemplate; Override public void onTaskStatusUpdate(String taskId, String newStatus, Integer progress, String resultUrl) { MapString, Object message new HashMap(); message.put(taskId, taskId); message.put(status, newStatus); message.put(progress, progress); message.put(resultUrl, resultUrl); // 推送到前端特定的主题例如 /topic/task/{taskId} messagingTemplate.convertAndSend(/topic/task/ taskId, message); } } /** * 观察者2数据库状态更新器 */ Component public class DatabaseStatusUpdater implements GenerationTaskObserver { Autowired private GenerationTaskRepository taskRepository; Override public void onTaskStatusUpdate(String taskId, String newStatus, Integer progress, String resultUrl) { GenerationTask task taskRepository.findByTaskId(taskId); if (task ! null) { task.setStatus(newStatus); task.setProgress(progress); if (resultUrl ! null) { task.setResultUrl(resultUrl); } taskRepository.save(task); } } } /** * 观察者3钉钉群机器人通知仅当任务完成或失败时 */ Component public class DingTalkNotifier implements GenerationTaskObserver { Override public void onTaskStatusUpdate(String taskId, String newStatus, Integer progress, String resultUrl) { if (SUCCESS.equals(newStatus) || FAILED.equals(newStatus)) { String message String.format(AI图像生成任务 [%s] 已%s。, taskId, SUCCESS.equals(newStatus) ? 完成 : 失败); if (SUCCESS.equals(newStatus) resultUrl ! null) { message 结果URL: resultUrl; } // 调用钉钉机器人发送消息的HTTP客户端 sendDingTalkMessage(message); } } private void sendDingTalkMessage(String message) { /* ... 实现HTTP调用 ... */ } }接下来我们需要一个被观察的对象也就是主题Subject。它负责维护一个观察者列表并在自身状态改变时通知所有观察者。/** * 被观察者任务状态管理器 */ Component public class GenerationTaskManager { // 存储所有注册的观察者 private ListGenerationTaskObserver observers new CopyOnWriteArrayList(); // 注册观察者 public void registerObserver(GenerationTaskObserver observer) { observers.add(observer); } // 移除观察者 public void removeObserver(GenerationTaskObserver observer) { observers.remove(observer); } /** * 核心方法通知所有观察者任务状态已更新 */ public void notifyObservers(String taskId, String newStatus, Integer progress, String resultUrl) { for (GenerationTaskObserver observer : observers) { // 在实际项目中这里最好用异步方式执行避免某个观察者阻塞影响其他观察者 try { observer.onTaskStatusUpdate(taskId, newStatus, progress, resultUrl); } catch (Exception e) { // 记录日志但不影响其他观察者 log.error(通知观察者 {} 失败, observer.getClass().getSimpleName(), e); } } } }3.2 在业务流中整合现在我们如何触发这个通知机制呢通常我们会有一个后台线程或定时任务定期轮询AI服务查询我们提交的那些任务的状态。Service public class TaskPollingService { Autowired private GenerationTaskManager taskManager; Autowired private AIServiceClient aiClient; // 单例客户端 Autowired private GenerationTaskRepository taskRepository; Scheduled(fixedDelay 5000) // 每5秒轮询一次 public void pollTaskStatus() { // 1. 从数据库查出所有“进行中”的任务ID ListString pendingTaskIds taskRepository.findPendingTaskIds(); for (String taskId : pendingTaskIds) { // 2. 调用AI服务查询状态 TaskStatusResponse statusResponse aiClient.queryTaskStatus(taskId); // 3. 如果状态有变化 if (statusHasChanged(taskId, statusResponse)) { // 4. 更新本地数据库这里也可以让DatabaseStatusUpdater做但为了效率直接更新 updateLocalTaskStatus(taskId, statusResponse); // 5. 关键一步通知所有观察者 taskManager.notifyObservers( taskId, statusResponse.getStatus(), statusResponse.getProgress(), statusResponse.getResultUrl() ); } } } // ... 其他辅助方法 }这样做的好处松耦合TaskPollingService完全不知道有哪些观察者它只负责通知GenerationTaskManager。新增一个观察者比如加一个企业微信通知只需要新建一个类实现接口并注册到管理器即可轮询服务代码无需任何修改。可扩展性通知逻辑可以无限扩展且彼此独立。符合开闭原则对扩展开放可以加新观察者对修改关闭不需要改主题或已有观察者的代码。4. 用单例模式管理API客户端最后我们来看看调用AI服务的HTTP客户端。这个客户端需要配置连接超时、读写超时、重试策略、连接池大小等。这些配置通常在整个应用中是统一的且创建和销毁一个HTTP客户端开销较大。我们显然不希望每次调用API都新建一个客户端也不希望有多个客户端实例导致连接池混乱。这时单例模式就非常合适了。我们确保一个类只有一个实例并提供一个全局访问点。在Spring Boot项目中我们通常利用其强大的IoC容器来管理单例Bean这比手动实现单例更安全、更符合规范。4.1 配置并声明单例Bean我们使用一个配置类来创建和配置这个HTTP客户端Bean。Spring默认会将Bean的作用域设置为singleton所以这天然就是一个单例。Configuration public class HttpClientConfig { Value(${ai.service.connect-timeout:10000}) private int connectTimeout; Value(${ai.service.read-timeout:30000}) private int readTimeout; Value(${ai.service.max-idle-connections:20}) private int maxIdleConnections; Value(${ai.service.keep-alive-duration:300}) private long keepAliveDuration; /** * 创建并配置一个全局唯一的OkHttpClient实例。 * Bean注解默认就是单例的。 */ Bean public OkHttpClient aiServiceHttpClient() { ConnectionPool connectionPool new ConnectionPool(maxIdleConnections, keepAliveDuration, TimeUnit.SECONDS); return new OkHttpClient.Builder() .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS) .readTimeout(readTimeout, TimeUnit.MILLISECONDS) .writeTimeout(readTimeout, TimeUnit.MILLISECONDS) .connectionPool(connectionPool) // 添加重试拦截器 .addInterceptor(new RetryInterceptor(3)) // 添加统一的认证Header拦截器 .addInterceptor(chain - { Request originalRequest chain.request(); Request newRequest originalRequest.newBuilder() .header(Authorization, Bearer getApiKey()) .header(Content-Type, application/json) .build(); return chain.proceed(newRequest); }) .build(); } private String getApiKey() { // 从安全的地方获取API Key如配置中心、环境变量 return System.getenv(AI_SERVICE_API_KEY); } } /** * 简单的重试拦截器 */ public class RetryInterceptor implements Interceptor { private int maxRetries; public RetryInterceptor(int maxRetries) { this.maxRetries maxRetries; } Override public Response intercept(Chain chain) throws IOException { Request request chain.request(); Response response null; IOException exception null; for (int i 0; i maxRetries; i) { try { response chain.proceed(request); if (response.isSuccessful()) { return response; } else if (i maxRetries) { // 最后一次重试仍然失败返回响应 return response; } // 非成功响应关闭body并重试 if (response.body() ! null) { response.close(); } } catch (IOException e) { exception e; if (i maxRetries) { throw exception; } } // 等待一段时间后重试 try { Thread.sleep(1000L * (i 1)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IOException(重试被中断, e); } } throw new IOException(请求失败已达最大重试次数); } }4.2 封装服务客户端有了单例的OkHttpClient我们再封装一个专门用于调用AI服务的客户端类。Service public class AIServiceClient { // 注入单例的HttpClient Autowired private OkHttpClient httpClient; Value(${ai.service.base-url}) private String baseUrl; public String submitTask(String endpoint, JSONObject requestBody) throws IOException { String url baseUrl endpoint; RequestBody body RequestBody.create(requestBody.toJSONString(), MediaType.get(application/json)); Request request new Request.Builder().url(url).post(body).build(); try (Response response httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { throw new IOException(Unexpected code response , body: response.body().string()); } String responseBody response.body().string(); // 解析响应获取任务ID JSONObject jsonResponse JSON.parseObject(responseBody); return jsonResponse.getString(task_id); } } public TaskStatusResponse queryTaskStatus(String taskId) throws IOException { String url baseUrl /api/v1/task/status?task_id taskId; Request request new Request.Builder().url(url).get().build(); try (Response response httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { throw new IOException(查询任务状态失败: response); } String responseBody response.body().string(); return JSON.parseObject(responseBody, TaskStatusResponse.class); } } // ... 其他方法 }这样做的好处资源高效整个应用共享一个连接池避免了频繁创建和销毁连接的开销。配置统一所有HTTP请求都遵循相同的超时、重试、认证策略行为一致。线程安全由Spring管理的单例Bean其依赖注入是线程安全的。OkHttpClient本身也被设计为线程安全可以被多线程共享。易于测试我们可以很容易地通过MockBean来模拟这个AIServiceClient对业务逻辑进行单元测试。5. 总结回过头来看我们通过一个调用AI图像生成服务的实际项目把面试中常考的三种设计模式实实在在地用了起来。工厂模式帮助我们优雅地应对了多变的业务需求让代码保持了良好的扩展性观察者模式让我们能够以松耦合的方式处理复杂的异步状态通知使得系统各部分职责清晰单例模式则确保了核心资源HTTP客户端的管理既高效又安全。你会发现设计模式从来都不是为了炫技而存在的“八股文”。它们是无数前辈工程师在解决特定类型问题时总结出来的最佳实践和“套路”。当你遇到“需要根据条件创建不同对象”、“一个对象状态变化需要通知多个其他对象”、“某个对象只需要一个实例”这类场景时这些模式就会自然而然地浮现在脑海中成为你工具箱里顺手的利器。下次面试再被问到设计模式你大可以结合这个“云容笔谈”项目的例子自信地告诉面试官我知道单例模式怎么避免连接池浪费工厂模式怎么让我的图像生成服务支持新风格而不用改核心代码观察者模式怎么把WebSocket推送、数据库更新和钉钉通知解耦开来。理论结合实践这才是“八股文”真正该有的样子。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。