观察者模式解耦核心是subject仅通知事件,不处理具体逻辑;observer各自响应,如emailservice发邮件、logservice记日志,update($event, $data)统一接口,事件名用'user.registered'等事实型命名,数据宜封装为value object。

Observer 接口和 Subject 实现必须分离关注点
观察者模式解耦的核心不是“加个 notify”,而是让业务类(如 User)完全不知道邮件怎么发、由谁发。Subject 只负责“我发生了什么”,不负责“这事该谁干”。常见错误是把 sendEmail() 直接写进 notify() 里,这等于又把邮件逻辑塞回去了。
正确做法是:Subject 持有 Observer 列表,仅调用 $observer->update($event, $data);每个 Observer 自行决定是否响应、如何响应。比如 EmailService 收到 'user.registered' 才发邮件,LogService 收到同一事件只记日志。
-
update()方法签名必须统一,推荐为update(string $event, array $data),避免类型判断污染逻辑 - Subject 的
attach()应允许重复注册相同 Observer(靠业务层去重),否则测试时容易因多次new导致监听失效 - 不要在
notify()中捕获 Observer 抛出的异常——异常应冒泡,由上层(如事件总线或请求生命周期)统一处理,否则一个 Observer 失败会静默吞掉其他 Observer 的执行
避免 Observer 内部同步阻塞主流程
即使用了 Observer,如果 EmailService::update() 里直接调用 PHPMailer::send(),用户注册接口依然会卡住。这不是模式的问题,是实现没走完解耦的最后一公里。
真正异步的关键在于:Observer 本身不执行耗时操作,只做“投递”动作。它应该把邮件任务推入队列(如 Redis List 或 RabbitMQ),然后立刻返回。
- Observer 内不应实例化
PHPMailer或调用mail(),而应使用think-queue的job或 Symfony Messenger 的dispatch() - 若项目无消息队列,至少用
pcntl_fork()或exec("nohup php sendmail.php ... > /dev/null 2>&1 &")脱离主进程,但注意资源清理和错误反馈缺失 - 务必给队列任务设置唯一 ID(如基于
$data['user_id'] . '_' . time()),防止重复消费时发重邮件
事件命名与数据结构要可演进
很多人把事件名写成 'user_register',后续加手机号验证后想改成 'user_registered_with_phone',结果所有 Observer 都得改判断逻辑。这不是解耦,是换种方式耦合。
事件名应表达“已完成的事实”,而非“触发的动作”,且保持粒度稳定。数据结构也需预留扩展性,别传裸数组。
- 事件名用英文点号分隔,如
'user.registered'、'order.shipped',便于按前缀订阅(Laravel 风格) -
$data推荐封装为 Value Object(如UserRegisteredEvent),而非关联数组,避免字段拼错或缺失时静默失败 - 不要在 Observer 里做数据库查询——如果需要用户完整信息,应该在 Subject 通知前就查好、塞进
$data;否则 Observer 各自查一次,N+1 问题就来了
第三方库介入时要注意容器绑定时机
用 Laravel 或 ThinkPHP 时,有人直接在 Observer 构造函数里 app()->make(Mailer::class),看似解耦,实则埋雷:如果 Mailer 依赖了某个刚初始化一半的配置服务,就会报错。
根本问题是 Observer 实例化早于框架完整启动。真正安全的做法是延迟解析,即在 update() 执行时才从容器取实例。
- 在 Observer 内保留
protected $container,构造时注入ContainerInterface,而不是具体服务 -
update()中用$this->container->make(MailerInterface::class),确保每次都是新鲜、已就绪的实例 - 若用 PHP-DI 或 Laravel,可直接类型提示接口,框架自动注入——但前提是 Observer 本身由容器管理,不能
new EmailService()
观察者模式本身不保证异步,它只解决“谁该知道”;真正让邮件不拖慢接口的,是 Observer 内部是否把“发邮件”这个动作移交给了另一个执行上下文。这点最容易被忽略,也是线上报警最常出现的地方。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











