设计模式之上下文对象设计模式
目录
一、模式介绍
二、架构设计
三、Demo 示例
四、总结
一、模式介绍
上下文对象(Context Object)模式 最早由《Core J2EE Patterns》第二版提出,其核心目标是在多层或多组件间共享与当前作用域(如一次请求、一次会话、一次业务流程)相关的所有状态和服务,消除各组件对底层环境细节(如协议、线程、本地存储等)的直接依赖,从而提高系统的可复用性、可维护性和可测试性。Context Object 封装了与某个 Scope 相关的数据与行为,使各层或模块只需依赖该 Context,即可获得所需服务或状态,而无需显式传递大量参数或直接引用环境 API。
使用场景与历史背景:
-
在传统多层应用中,每一层如果要访问共享信息(如用户凭证、事务、配置参数),通常需要将其作为参数显式传递,导致方法签名臃肿、可读性差、易出错。
-
随着 J2EE 应用复杂度提升,设计者发现将环境细节直接嵌入业务逻辑耦合度过高,维护成本激增。
-
《Core J2EE Patterns》第二版总结了当时业界实践,将这种“共享环境数据”抽象为 Context Object 模式,对应 ApplicationContext/ServletContext、Hibernate SessionContext 等典型用法。
解决问题:
-
解耦:业务组件不必直接依赖环境(HTTP、JNDI、线程等)接口,一律通过 Context 获取所需信息或服务。
-
集中管理:所有上下文相关的信息集中维护,便于调试、监控及扩展。
-
生命周期一致性:Context 在作用域开始时创建,结束时统一销毁,资源释放更可控。
典型实现:
-
Apache Spark –
SparkContext
/SQLContext
/StreamingContext
聚合集群配置、调度、序列化、监控接口,并暴露 RDD/DataFrame/流计算等 API。
-
Flink –
StreamExecutionEnvironment
/ExecutionEnvironment
封装执行引擎配置、并行度、checkpoint、状态后端等,用于构建批/流作业。
二、架构设计
以下为 Context Object 模式的标准 UML 类图:
主要组件说明:
-
Context(接口):定义获取与存放上下文数据的方法。
-
ConcreteContext(实现类):内部维护一张属性映射,负责实际存取。
-
ContextFactory(工厂类):(可选)根据环境创建对应的 Context 实例。
-
Client:业务组件,通过构造器或工厂获取 Context 实例,并向其读写数据或调用存入的服务。
三、Demo 示例
问题场景:在一个 Web 应用或微服务中,常常需要在多个业务层(如控制层、服务层、数据访问层)之间传递用户会话信息、跟踪操作日志对象以及可复用的工具服务实例。直接在每个方法签名中传递这些参数,不仅会导致参数列表臃肿,还容易遗漏,增加维护成本。
解决方案:使用上下文对象模式,将所有需跨层共享的数据与服务封装到一个 Context 对象中,由各层直接从 Context 中获取,而不必在方法之间显式传递这些参数。
// 1. 定义 Context 接口,提供读写属性和服务获取功能 public interface RequestContext {<T> T get(String key, Class<T> type);void set(String key, Object value); } // 2. Context 实现,内部维护属性映射 public class RequestContextImpl implements RequestContext {private final Map<String, Object> data = new HashMap<>(); @Overridepublic <T> T get(String key, Class<T> type) {return type.cast(data.get(key));} @Overridepublic void set(String key, Object value) {data.put(key, value);} } // 3. Context 工厂,初始化必要属性 public class RequestContextFactory {public static RequestContext create(String userId) {RequestContext ctx = new RequestContextImpl();// 初始化用户信息和日志追踪器ctx.set("userId", userId);ctx.set("traceId", UUID.randomUUID().toString());return ctx;} } // 4. 控制层:创建 Context 并启动业务流程 public class Controller {public void handleRequest(String userId) {RequestContext ctx = RequestContextFactory.create(userId);new BusinessService(ctx).process();} } // 5. 业务层:直接从 Context 获取 userId 和 TraceId,执行业务逻辑 public class BusinessService {private final RequestContext ctx; public BusinessService(RequestContext ctx) {this.ctx = ctx;} public void process() {String userId = ctx.get("userId", String.class);String traceId = ctx.get("traceId", String.class);// 输出日志时无需额外传参System.out.println("[" + traceId + "] Processing business logic for user " + userId); // 调用下一层服务new DataService(ctx).execute();} } // 6. 数据访问层:继续复用同一个 Context public class DataService {private final RequestContext ctx; public DataService(RequestContext ctx) {this.ctx = ctx;} public void execute() {String traceId = ctx.get("traceId", String.class);// 使用 traceId 进行 SQL 日志关联System.out.println("[" + traceId + "] Executing SQL queries");} }
说明:
-
Controller
层只需创建并初始化一次RequestContext
,并传递给后续各层,不再维护多个参数。 -
任何业务或数据访问类均可通过
ctx.get(...)
获取所需信息,简化方法签名。 -
可扩展到添加更多上下文数据(如用户权限列表、国际化配置、第三方服务客户端等),只需在 Context 中新增属性,而无需改动各层接口。
四、总结
上下文对象模式 通过封装作用域相关的所有状态与服务,实现组件与环境的最大解耦,提升系统的灵活性和可维护性。
-
价值:
-
模块化:各功能模块仅依赖 Context 接口,无须关注如何获取底层资源。
-
扩展性:新增上下文数据或服务时,只需修改 Context 实现,无需改动业务层代码。
-
测试友好:可为单元测试提供 Mock Context,对业务逻辑进行隔离测试。
-
-
注意事项:
-
避免 Context 过于庞大,必要时可拆分为多个子 Context(如 ConfigContext、SecurityContext)。
-
随着系统复杂度上升,建议引入成熟的 IoC/DI 容器(如 Spring
ApplicationContext
)来管理对象生命周期和依赖注入。
-
Context Object 模式是企业级应用架构中常见且行之有效的方案,既可手写实现,也可借助框架。它在《Core J2EE Patterns》一书中的提炼,为现代微服务与云原生开发中的“作用域数据共享”提供了理论基础与实践指导。