ShippingServiceInterface 是整个策略模式运行的底线,必须先定义严格接口(如 track(string $trackingNumber): array{status: string, data: array}),再实现各物流策略;工厂仅按规则选实例,不执行业务逻辑;策略须无状态、依赖注入、独立超时与错误映射。

ShippingServiceInterface必须存在,否则所有策略类无法被统一调用——这是整个结构能跑起来的底线。
接口定义必须严格、具体、不可省略
别跳过这步直接写实现类。没有 ShippingServiceInterface,后续任何「替换策略」都只是空谈。它不是摆设,而是类型约束和 IDE 自动补全的基础。
- 接口方法签名要明确:比如
track(string $trackingNumber): array,不能是track($id)这种模糊写法 - 返回值类型建议用 PHP 8+ 的联合类型或数组形状,例如
array{status: string, data: array},避免下游反复is_array()判断 - 如果物流商返回结构差异大(菜鸟返回 JSON,顺丰返回 XML),应在接口层就约定统一输出格式,转换逻辑收进具体策略内部,而非暴露给上下文
ShippingServiceFactory 只负责选,不负责造细节
工厂类的核心职责是根据单号前缀、区域码、或配置项,返回一个已实现 ShippingServiceInterface 的实例。它不该做 HTTP 请求、不处理重试、不解析响应体。
- 判断逻辑尽量轻量:用
str_starts_with()(PHP 8.0+)或substr($n, 0, 2) === 'SF',别写正则匹配单号规则 - 别在工厂里 new 出来就立刻调用
track()——那等于把策略执行和选择耦死了 - 异常要抛具体类型,比如
throw new UnsupportedTrackingNumberException($trackingNumber),而不是笼统的Exception
每个策略类必须无状态、只依赖传入参数
极兔策略类改了缓存开关,导致菜鸟策略下一次请求拿到脏数据——这种问题在线上查三天都不一定能复现。策略类之间不能共享 static、全局变量、或单例里的可变状态。
- 所有外部依赖(如
Logger、HttpClient)必须通过构造函数注入,且每次 new 都是新实例 - 如果多个策略都要用同一个
CacheInterface,那就把它作为参数传进去,而不是在策略内部Cache::get() - 测试时两个策略单元测试并行跑失败?第一反应就该检查有没有静态属性或文件锁残留
上线前最容易漏掉的兼容点:错误码映射与超时控制
不同物流商对「查无此单」的 HTTP 状态码、JSON 错误字段名、重试建议各不相同。策略模式不解决语义统一,得靠你在每个策略里手动对齐。
- 菜鸟返回
{"code": 5001, "msg": "not found"},顺丰可能是{"result": false, "error": "NO_TRACKING"},这些要各自在对应策略中转成一致的['success' => false, 'reason' => 'not_found'] - 别共用一个
timeout=5:极兔接口平均 200ms,顺丰可能卡到 3s,每个策略应独立配置$this->client->setTimeout(3.0) - 别让
track()方法抛出未捕获的Throwable,统一兜底为返回含error键的数组,否则上下文会崩
实际部署时,最常出问题的不是策略怎么写,而是工厂没配好 fallback,或者某个策略忘了加 try/catch 把网络异常吞掉。接口协议和错误处理这两块,没法靠模式自动兜住。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











