1. 从“context-mode”说起:一个被低估的工程概念
第一次看到“context-mode”这个词,很多人会下意识地把它归到某个具体框架的配置项里,比如某个前端库的渲染模式、某个数据库的连接上下文、或者某个AI工具的对话上下文开关。但如果你真的在工程一线待过几年,就会发现“context-mode”其实是一个跨领域的通用设计思路——它描述的是系统在不同上下文环境下切换行为模式的能力。这个能力听起来很抽象,但落到实际项目里,它决定了你的代码是“一套逻辑打天下”还是“因地制宜、随境而变”。
我最早接触这个概念是在做一个多租户后台系统的时候。当时的需求是:同一套业务代码,既要跑在Web端给管理员用,又要跑在移动端给一线员工用,还要跑在定时任务里做批处理。三个场景对数据精度、响应速度、权限校验的要求完全不同。如果每个场景都写一套独立逻辑,维护成本会爆炸;如果强行统一,又会出现“移动端加载了管理员才需要的全量字段”这种浪费。最后我们引入的就是context-mode的思路:用上下文标识驱动同一套核心逻辑走不同的分支策略。
所以这篇文章想聊的,不是某个具体库的API怎么调,而是context-mode这个设计模式本身——它解决什么问题、在哪些场景下特别管用、落地时有哪些坑、以及怎么用最小的代价把它集成到现有项目里。无论你是做后端服务、前端应用、数据处理管道,还是做AI应用开发,只要你的系统需要“看情况办事”,context-mode就值得你花时间研究。接下来的内容会从设计思路、核心实现、实操步骤、问题排查四个维度展开,尽量把每个决策背后的“为什么”讲清楚,让你看完能直接在自己的项目里试起来。
2. 内容整体设计与思路拆解
2.1 为什么需要context-mode:三种典型困境
在讲具体实现之前,先搞清楚context-mode到底在解决什么问题。我总结下来,它主要应对三种典型困境。
第一种是多端行为差异。同一个业务逻辑,在Web端、移动端、小程序、桌面客户端上的表现要求不一样。比如一个商品列表接口,Web端需要返回完整的库存、促销、评价摘要;移动端可能只需要主图、价格和标题;而内部的数据分析任务则需要全量字段加上历史快照。如果不用context-mode,你要么写三个接口,要么在一个接口里堆满if-else判断请求来源。前者导致逻辑重复,后者导致代码难以维护。
第二种是环境敏感的执行策略。开发环境、测试环境、预发布环境、生产环境,对日志级别、缓存策略、超时时间、重试次数的要求完全不同。开发环境希望日志越详细越好,生产环境则要控制日志量避免磁盘打满;测试环境可以容忍较长的超时,生产环境必须快速失败。这些差异如果硬编码在代码里,每次环境切换都要改代码重新部署,风险极高。context-mode的做法是:把环境差异抽象成上下文配置,代码只读配置不写死值。
第三种是用户角色与权限的细粒度控制。同一个功能,普通用户、VIP用户、管理员、超级管理员看到的界面和能执行的操作都不一样。传统的RBAC模型能解决“能不能访问”的问题,但解决不了“访问时展示什么”的问题。context-mode可以结合用户角色,在数据查询、序列化、渲染各个层面动态调整行为。
这三种困境的共同点是:核心逻辑相同,但外围行为需要随上下文变化。context-mode的价值就在于,它提供了一套统一的机制来管理这些变化,而不是让变化散落在代码的各个角落。
2.2 核心设计原则:上下文即配置,模式即策略
理解了问题,再来看context-mode的设计原则。我把它归纳成两句话:上下文即配置,模式即策略。
“上下文即配置”的意思是,所有影响行为的外部因素——请求来源、环境标识、用户角色、时间窗口、租户ID——都应该被收集到一个统一的上下文对象里,而不是散落在各个函数参数或全局变量中。这个上下文对象应该是不可变的,一旦创建就不能被修改,避免在传递过程中被意外篡改。同时它应该是可序列化的,方便在分布式系统中跨服务传递。
“模式即策略”的意思是,针对每一种上下文组合,系统应该有一组明确的策略来定义行为。这些策略可以是简单的键值对配置,也可以是复杂的策略对象。关键在于,策略的选取应该是声明式的,而不是命令式的。也就是说,代码不应该写“如果环境是生产且用户是管理员,则执行A;否则执行B”,而应该写“根据当前上下文,从策略注册表中获取对应的策略,然后执行策略定义的行为”。
这种设计的好处是显而易见的。首先,新增一种上下文组合时,只需要注册新的策略,不需要修改核心逻辑。其次,策略可以被单独测试,不需要构造完整的请求链路。最后,策略可以被动态调整,比如通过配置中心下发新的策略,而不需要重新部署服务。
2.3 方案选型:从硬编码到上下文驱动的演进路径
在实际项目中落地context-mode,通常有三种方案可选,复杂度依次递增。
方案一:环境变量加条件分支。这是最简单的做法,用环境变量标识当前环境,然后在代码里写if-else。优点是实现快,缺点是随着上下文维度增加,条件分支会爆炸式增长,最终变成无法维护的“面条代码”。这种方案只适合上下文维度单一且变化不频繁的场景。
方案二:策略模式加工厂方法。这是比较经典的面向对象做法。定义一个策略接口,每个上下文组合对应一个策略实现类,然后用一个工厂根据上下文创建策略实例。优点是结构清晰,符合开闭原则;缺点是需要写很多类,对于简单场景来说过于重量级。
方案三:上下文对象加策略注册表。这是我个人最推荐的方案,也是很多现代框架采用的做法。核心思路是:上下文对象负责收集和传递信息,策略注册表负责根据上下文匹配策略,策略本身可以是函数、配置对象或类实例。这种方案兼顾了灵活性和简洁性,既不需要写大量类,又能保持逻辑清晰。
选择哪种方案,取决于你的项目规模和团队习惯。如果是小型项目或者原型验证,方案一够用;如果是中型项目且团队熟悉设计模式,方案二是不错的选择;如果是大型项目或者需要频繁调整策略,方案三的扩展性最好。接下来的实操部分,我会以方案三为主来展开,因为它的适用面最广,也最能体现context-mode的精髓。
3. 核心细节解析与实操要点
3.1 上下文对象的字段设计与生命周期管理
上下文对象是context-mode的基石,它的字段设计直接决定了系统的灵活性和可维护性。根据我的经验,一个通用的上下文对象至少应该包含以下几类字段。
来源标识类:请求来源(Web、移动端、内部服务)、客户端版本、渠道标识。这类字段用于区分不同端的差异化行为。
环境标识类:运行环境(开发、测试、生产)、区域标识、部署版本。这类字段用于控制环境相关的策略。
用户标识类:用户ID、角色列表、租户ID、权限等级。这类字段用于权限和数据隔离。
时间标识类:请求时间、业务时间窗口、时区。这类字段用于处理时间敏感的逻辑。
追踪标识类:请求ID、链路追踪ID、会话ID。这类字段用于日志和监控。
字段设计的原则是:只放影响行为的字段,不放业务数据。上下文对象不是请求参数的容器,而是决策依据的集合。如果把业务数据也塞进去,上下文对象会变得臃肿,传递成本也会增加。
生命周期管理方面,上下文对象应该在请求入口处创建,然后沿着调用链向下传递。在Web框架中,通常通过中间件或拦截器来创建上下文,然后挂载到请求对象上。在服务间调用时,上下文需要序列化到请求头或消息体中,由下游服务反序列化后重建。这里有个关键点:上下文在传递过程中应该是只读的。如果下游服务需要修改上下文,应该创建一个新的上下文对象,而不是修改原有的。这样可以避免并发场景下的数据竞争问题。
注意:上下文对象的字段不宜过多,一般控制在10到15个以内。字段太多会导致序列化开销增大,也会让策略匹配变得复杂。如果确实需要更多信息,可以考虑把部分字段放到业务参数中,而不是上下文里。
3.2 策略注册表的匹配算法与优先级规则
策略注册表是context-mode的核心组件,它负责根据上下文匹配到正确的策略。匹配算法的设计直接影响系统的性能和可预测性。
最简单的匹配算法是精确匹配:上下文的每个字段都必须与策略声明的条件完全一致。这种算法的优点是结果确定,缺点是策略数量会随着上下文维度组合爆炸。比如三个维度各有三个取值,就需要27个策略。
更实用的算法是优先级匹配:策略声明时指定优先级,匹配时按优先级从高到低依次尝试,第一个匹配成功的策略被选中。这种算法允许你为特定组合定义精确策略,同时为通用情况定义兜底策略。优先级的设定通常遵循“越具体越优先”的原则。
还有一种算法是评分匹配:每个策略根据匹配的字段数量和字段权重计算得分,得分最高的策略被选中。这种算法最灵活,但也最难调试,因为策略的选中结果不容易预测。
我个人的建议是:优先使用优先级匹配,配合精确匹配作为补充。具体做法是,为每个策略声明一个优先级数字,数字越大优先级越高。匹配时先按优先级排序,然后依次检查策略条件是否满足。策略条件可以只声明部分字段,未声明的字段视为通配。这样既能处理精确场景,又能处理通用场景。
优先级规则的设计需要注意几点。首先,兜底策略的优先级应该最低,确保它只在没有其他策略匹配时才生效。其次,同一优先级的策略不应该有重叠条件,否则选中结果取决于注册顺序,这是不可靠的。最后,策略的优先级应该集中管理,不要散落在各个策略定义中,方便全局调整。
3.3 策略的实现形式:函数、配置与类的取舍
策略的实现形式有三种常见选择:函数、配置对象和类实例。每种形式都有其适用场景。
函数形式是最轻量的。一个策略就是一个函数,接收上下文和业务参数,返回处理结果。优点是定义简单、调用直接;缺点是无法携带状态,也不容易做复杂的生命周期管理。函数形式适合逻辑简单、无状态的策略,比如“根据环境返回不同的日志级别”。
配置对象形式适合那些只是参数差异的策略。比如不同环境下的超时时间、重试次数、缓存TTL,这些都可以用配置对象来表达。配置对象的优点是修改方便,可以通过配置中心动态下发;缺点是只能表达静态差异,无法处理需要计算或条件判断的逻辑。
类实例形式是最重量级的,但也是最灵活的。一个策略类可以封装复杂的状态和逻辑,可以实现接口来支持生命周期回调。类实例适合那些需要维护状态、需要依赖注入、或者逻辑复杂的策略。比如一个数据脱敏策略,可能需要根据用户角色和字段类型动态决定脱敏规则,这种场景用类实例来实现会更清晰。
在实际项目中,这三种形式往往混合使用。我的经验是:能用配置对象解决的,不要用函数;能用函数解决的,不要用类。这样可以让系统保持简洁,避免过度设计。只有当策略确实需要状态或复杂逻辑时,才引入类实例。
3.4 上下文传递的序列化与跨服务一致性
在分布式系统中,上下文需要在服务之间传递。传递的方式通常有两种:通过请求头传递,或者通过消息体传递。无论哪种方式,都需要解决序列化和一致性问题。
序列化方面,推荐使用紧凑的二进制格式或JSON。二进制格式体积小、解析快,但可读性差;JSON可读性好、兼容性强,但体积较大。如果上下文字段不多,JSON完全够用;如果字段很多且调用频繁,可以考虑用Protobuf或MessagePack。
跨服务一致性的关键在于:上下文在传递过程中不能被篡改。如果上游服务传递的上下文被下游服务修改,会导致行为不一致。解决方案是给上下文加上签名,下游服务验证签名后才使用。签名可以用HMAC实现,密钥由所有服务共享。
另一个问题是上下文字段的兼容性。当上游服务新增了一个上下文字段,下游服务可能还不认识这个字段。这时候下游服务应该忽略未知字段,而不是报错。同样,当下游服务需要的字段在上游不存在时,应该使用默认值,而不是抛出异常。这种“宽容读取”的策略可以保证系统在滚动升级时的兼容性。
提示:上下文传递时,建议只传递必要的字段,不要传递敏感信息。如果确实需要传递敏感信息,应该先加密再传递,并在下游服务中解密后使用。
4. 实操过程与核心环节实现
4.1 环境准备与基础依赖安装
在开始实操之前,先说明一下环境要求。这套方案不依赖特定的编程语言或框架,我用Python来演示,因为它的表达比较直观。如果你用Java、Go或Node.js,思路是一样的,只是语法不同。
基础依赖方面,只需要标准库就够了。如果要用配置中心动态下发策略,可以额外引入一个配置客户端库。如果要做上下文签名,需要引入一个加密库。这些都不是必须的,可以根据实际需求决定。
项目结构建议这样组织:一个context模块负责上下文对象的定义和传递,一个strategy模块负责策略的定义和注册,一个registry模块负责策略的匹配和选取。业务代码只依赖context和registry,不直接依赖具体的策略实现。这样可以让策略的实现和业务逻辑解耦。
# context/context.py from dataclasses import dataclass, field from typing import Optional @dataclass(frozen=True) class Context: source: str = "unknown" env: str = "dev" user_id: Optional[str] = None roles: tuple = field(default_factory=tuple) tenant_id: Optional[str] = None trace_id: Optional[str] = None这个上下文对象是冻结的(frozen=True),创建后不能修改。字段设计遵循前面说的原则:只放影响行为的字段。roles用元组而不是列表,也是为了保持不可变性。
4.2 策略注册表的完整实现
策略注册表的核心是一个有序列表,存储所有已注册的策略。每个策略包含三个部分:优先级、匹配条件、执行逻辑。
# strategy/registry.py from typing import Callable, Any, Dict, List from context.context import Context class Strategy: def __init__(self, name: str, priority: int, condition: Dict[str, Any], handler: Callable): self.name = name self.priority = priority self.condition = condition self.handler = handler def matches(self, ctx: Context) -> bool: for key, expected in self.condition.items(): actual = getattr(ctx, key, None) if actual != expected: return False return True class StrategyRegistry: def __init__(self): self._strategies: List[Strategy] = [] def register(self, strategy: Strategy): self._strategies.append(strategy) self._strategies.sort(key=lambda s: s.priority, reverse=True) def resolve(self, ctx: Context) -> Strategy: for strategy in self._strategies: if strategy.matches(ctx): return strategy raise LookupError(f"No strategy matched for context: {ctx}")这段代码的关键点在于:register方法在每次注册后都会重新排序,确保resolve时按优先级从高到低遍历。matches方法只检查策略声明的字段,未声明的字段视为通配。这样设计的好处是,你可以为特定组合定义精确策略,同时为通用情况定义兜底策略。
4.3 一个完整的业务场景落地示例
假设我们有一个商品查询服务,需要根据请求来源和用户角色返回不同的字段集。Web端管理员需要全量字段,移动端普通用户只需要基础字段,内部服务需要包含成本价。
首先定义策略:
# strategies/product_strategies.py from strategy.registry import Strategy def full_fields_handler(ctx, product): return { "id": product["id"], "name": product["name"], "price": product["price"], "cost": product["cost"], "stock": product["stock"], "promotions": product.get("promotions", []), } def basic_fields_handler(ctx, product): return { "id": product["id"], "name": product["name"], "price": product["price"], } def internal_fields_handler(ctx, product): return { "id": product["id"], "name": product["name"], "price": product["price"], "cost": product["cost"], "stock": product["stock"], } strategies = [ Strategy( name="internal_full", priority=100, condition={"source": "internal"}, handler=internal_fields_handler, ), Strategy( name="web_admin_full", priority=90, condition={"source": "web", "roles": ("admin",)}, handler=full_fields_handler, ), Strategy( name="mobile_basic", priority=80, condition={"source": "mobile"}, handler=basic_fields_handler, ), Strategy( name="default_basic", priority=0, condition={}, handler=basic_fields_handler, ), ]然后注册并调用:
# main.py from context.context import Context from strategy.registry import StrategyRegistry from strategies.product_strategies import strategies registry = StrategyRegistry() for s in strategies: registry.register(s) def query_product(ctx: Context, product_id: str): product = load_product_from_db(product_id) strategy = registry.resolve(ctx) return strategy.handler(ctx, product) # 模拟不同上下文 web_admin_ctx = Context(source="web", roles=("admin",), env="prod") mobile_ctx = Context(source="mobile", env="prod") internal_ctx = Context(source="internal", env="prod") print(query_product(web_admin_ctx, "P001")) print(query_product(mobile_ctx, "P001")) print(query_product(internal_ctx, "P001"))这个例子的关键设计在于:策略的匹配条件只声明必要的字段。internal_full策略只要求source是internal,不关心角色和环境。web_admin_full策略要求source是web且角色包含admin。mobile_basic策略只要求source是mobile。最后的default_basic策略条件为空,匹配所有上下文,作为兜底。
优先级的设计遵循“越具体越优先”的原则。internal_full最具体,优先级最高;web_admin_full次之;mobile_basic再次;default_basic最低。这样即使新增了其他来源,也能被兜底策略处理,不会出现无策略可用的情况。
4.4 参数计算与性能考量
策略匹配的性能主要取决于两个因素:策略数量和匹配条件的复杂度。假设有N个策略,每个策略平均有M个条件字段,那么最坏情况下需要比较N*M次。对于大多数项目来说,N在几十到几百之间,M在几个以内,这个开销完全可以忽略。
但如果策略数量达到几千个,就需要考虑优化了。常见的优化手段有三种。第一种是索引化,把策略按某个高频字段分组,匹配时先定位到组,再在组内遍历。第二种是缓存化,把上下文到策略的映射缓存起来,相同的上下文直接返回缓存的策略。第三种是预编译,把策略条件编译成更高效的匹配函数,减少属性访问开销。
我个人的经验是:在策略数量少于500个时,不需要做任何优化。直接遍历的性能足够好,而且代码简单,容易调试。只有当性能测试表明策略匹配成为瓶颈时,才考虑引入优化。过早优化会让代码复杂化,得不偿失。
注意:策略的匹配条件中,避免使用复杂的表达式或函数调用。条件应该是简单的等值比较,最多加上包含判断。复杂的条件逻辑应该放到策略的handler里,而不是匹配条件里。这样可以让匹配过程保持高效和可预测。
5. 常见问题与排查技巧实录
5.1 策略不生效的排查思路
策略不生效是使用context-mode时最常见的问题。根据我的经验,排查应该按照以下顺序进行。
第一步:确认上下文是否正确创建。在请求入口处打印上下文对象,检查字段值是否符合预期。常见的问题是上下文创建时字段赋值错误,比如把source写成了src,或者roles传了字符串而不是元组。
第二步:确认策略是否已注册。在注册完成后打印注册表中的策略列表,检查目标策略是否在列表中。常见的问题是策略定义在某个模块中,但该模块没有被导入,导致注册代码没有执行。
第三步:确认匹配条件是否满足。手动调用策略的matches方法,传入上下文,检查返回值。如果返回False,逐个字段对比上下文值和条件值。常见的问题是条件值类型不匹配,比如条件是字符串"admin",但上下文中的角色是元组("admin",)。
第四步:确认优先级是否正确。如果多个策略都匹配,检查选中策略的优先级是否最高。常见的问题是兜底策略的优先级设置过高,导致它抢占了精确策略的匹配机会。
第五步:确认handler是否被正确调用。在handler中打印日志,确认它被执行了。常见的问题是handler内部抛出了异常,但被上层捕获后静默处理了。
5.2 上下文传递丢失的典型场景
上下文在跨服务传递时丢失,是分布式系统中的高频问题。典型场景有以下几种。
场景一:异步任务中上下文丢失。当请求处理过程中启动了异步任务(比如线程池、协程、消息队列),上下文不会自动传递到异步任务中。解决方案是在创建异步任务时,显式地把上下文作为参数传入,或者在异步任务的入口处从请求上下文中恢复。
场景二:服务间调用时上下文丢失。当服务A调用服务B时,如果A没有把上下文序列化到请求头中,B就无法获取上下文。解决方案是在HTTP客户端或RPC框架中增加拦截器,自动把上下文注入到请求头中。
场景三:中间件顺序导致上下文丢失。如果上下文创建中间件在认证中间件之后执行,那么认证中间件就无法访问上下文。解决方案是调整中间件顺序,确保上下文创建中间件在最前面执行。
场景四:上下文被意外覆盖。如果下游服务在处理请求时重新创建了上下文,而不是从请求头中恢复,那么上游传递的上下文就会丢失。解决方案是统一上下文的创建入口,只在请求入口处创建,其他地方只读取不创建。
5.3 策略冲突与优先级调整的实战经验
策略冲突是指多个策略同时匹配一个上下文,导致选中结果不符合预期。这种情况通常发生在策略条件有重叠时。
比如有两个策略:策略A的条件是source=web,优先级是50;策略B的条件是roles=admin,优先级是60。当一个来自Web端的管理员请求到达时,两个策略都匹配,但策略B的优先级更高,所以被选中。如果这不是你期望的结果,就需要调整优先级。
调整优先级的原则是:越具体的策略优先级越高。具体性可以通过条件字段的数量来衡量,字段越多越具体。也可以通过业务重要性来衡量,关键业务的策略优先级更高。
在实际项目中,我建议把优先级定义成常量,集中放在一个文件里,方便全局查看和调整。不要在各个策略定义中硬编码优先级数字,否则时间长了很难维护。
# strategy/priorities.py PRIORITY_INTERNAL = 100 PRIORITY_WEB_ADMIN = 90 PRIORITY_MOBILE = 80 PRIORITY_DEFAULT = 0这样调整优先级时只需要改一个地方,而且优先级之间的关系一目了然。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 策略不生效 | 上下文字段值错误 | 打印上下文对象 | 检查字段赋值逻辑 |
| 策略不生效 | 策略未注册 | 打印注册表策略列表 | 确保策略模块被导入 |
| 策略不生效 | 匹配条件不满足 | 手动调用matches方法 | 逐字段对比类型和值 |
| 策略不生效 | 优先级被抢占 | 打印选中策略名称 | 调整优先级或条件 |
| 上下文丢失 | 异步任务未传递 | 在异步任务中打印上下文 | 显式传递上下文参数 |
| 上下文丢失 | 服务间未传递 | 检查请求头 | 增加拦截器注入上下文 |
| 上下文丢失 | 中间件顺序错误 | 检查中间件注册顺序 | 调整上下文中间件到最前 |
| 策略冲突 | 条件重叠 | 打印所有匹配的策略 | 调整优先级或细化条件 |
| 性能下降 | 策略数量过多 | 统计策略数量和匹配耗时 | 引入索引或缓存优化 |
提示:这张表可以打印出来贴在工位上,遇到问题时按表排查,能节省大量时间。我自己的团队就把这张表做成了内部wiki的置顶页面,新同事上手时先看这个,基本能解决八成以上的常见问题。
6. 进阶扩展与个人实践体会
6.1 动态策略下发与热更新
当策略需要频繁调整时,把策略硬编码在代码里就不合适了。这时候可以考虑动态策略下发:把策略定义放在配置中心,服务启动时拉取,运行时监听变更。
实现动态下发的关键点是:策略的注册和注销要支持运行时操作。注册表需要提供unregister方法,并且要处理好并发问题。当配置变更时,先构建新的策略列表,然后原子性地替换旧列表,避免匹配过程中出现中间状态。
配置的格式可以用JSON或YAML,每个策略包含名称、优先级、条件和handler标识。handler标识对应代码中预定义的函数名,通过反射或注册表来查找。这样配置只负责声明策略的元信息,具体的执行逻辑仍然在代码中,保证了安全性和可测试性。
6.2 策略的单元测试与集成测试
策略的测试是保证系统正确性的关键。单元测试应该覆盖每个策略的匹配逻辑和执行逻辑,集成测试应该覆盖策略注册表的匹配优先级和兜底行为。
单元测试的写法很简单:构造上下文对象,调用策略的matches方法,断言返回值;然后调用handler,断言输出结果。集成测试则需要构造多个策略,注册到注册表中,然后用不同的上下文调用resolve方法,断言选中的策略名称。
我习惯用参数化测试来覆盖多种上下文组合,这样可以用少量代码覆盖大量场景。比如用pytest的@pytest.mark.parametrize装饰器,把上下文和期望的策略名称作为参数传入,一次运行就能验证所有组合。
6.3 我在实际项目中的三点体会
第一点体会是:context-mode的价值在项目中期才会显现。项目初期,上下文维度少,策略简单,你可能会觉得这套机制有点过度设计。但随着业务发展,上下文维度增加,策略变复杂,这时候context-mode的优势就体现出来了——新增策略不需要改核心逻辑,调整行为不需要重新部署。所以如果你的项目有长期演进的预期,早点引入context-mode是值得的。
第二点体会是:上下文对象的字段要克制。我见过一些项目,把请求参数、用户信息、业务数据全部塞进上下文对象,导致上下文变得极其臃肿。这不仅增加了序列化开销,也让策略匹配变得复杂。正确的做法是:上下文只放影响行为的字段,业务数据通过参数传递。判断标准很简单:如果一个字段的变化不会导致策略切换,它就不应该出现在上下文里。
第三点体会是:策略的粒度要适中。策略太粗,会导致一个策略里堆满if-else,失去了context-mode的意义;策略太细,会导致策略数量爆炸,维护成本上升。我的经验是:一个策略只负责一个维度的行为差异,跨维度的组合通过优先级匹配来处理。比如“环境相关的日志级别”是一个策略,“角色相关的字段脱敏”是另一个策略,两者独立定义,通过优先级协调。
最后分享一个小技巧:在策略的handler中,可以再次读取上下文,根据上下文的细微差异做二次调整。这样可以在保持策略数量的同时,处理更细粒度的差异。比如一个“移动端基础字段”策略,可以在handler中判断用户是否是VIP,如果是则额外返回VIP专属字段。这种“策略加微调”的模式,在实践中非常实用。