当前位置: 首页 > news >正文

【[特殊字符][特殊字符] 协变与逆变:用“动物收容所”讲清楚 PHP 类型的“灵活继承”】

你有没有遇到过这样的问题:

“为什么子类方法可以返回 Cat,而父类只写了返回 Animal?”
“为什么参数反而能从 CatFood 变成更宽泛的 Food?”

这些看似“违反直觉”的设计,其实背后有一个优雅的编程概念:协变与逆变

别被名字吓到!今天我们不用术语堆砌,而是用一个“动物收容所”的故事,把这两个概念讲得清清楚楚,并明确说明它们在不同 PHP 版本中的支持情况。


🏡 故事开始:开一家动物收容所

假设你开了一个“动物收容所”,专门帮助流浪猫狗找主人。

你定义了一个基本的规则:

abstract class Animal {protected string $name;public function __construct(string $name) {$this->name = $name;}abstract public function speak();
}class Cat extends Animal {public function speak() {echo $this->name . " 喵喵叫";}
}class Dog extends Animal {public function speak() {echo $this->name . " 汪汪叫";}
}

一切都很正常。现在,你想让收容所支持“领养”功能。


🌱 第一幕:协变(Covariance)——返回值可以“更具体”

你设计了一个接口:

interface AnimalShelter {public function adopt(string $name): Animal;
}

意思是:任何收容所,都能领养一只“动物”

但具体实现时:

class CatShelter implements AnimalShelter {public function adopt(string $name): Cat {return new Cat($name);}
}class DogShelter implements AnimalShelter {public function adopt(string $name): Dog {return new Dog($name);}
}

注意!父接口说“返回 Animal”,子类却返回了更具体的 CatDog

❓这合法吗?
合法!这就是“协变”

✅ 协变的核心思想:

返回值可以变得更“具体”

就像你说:“我要领养一只动物。”
收容所说:“给你一只猫。”
👉 没问题!猫当然是动物。

🔍 技术上:CatAnimal 的子类,所以更“窄”、更“具体”,返回它是安全的。

这是 协变(Covariance)
协 = 协同,方向一致 —— 类型从“动物”变成“猫”,越来越具体,方向一致。

注意:完整的协变支持是从 PHP 7.4 开始的。


🍽️ 第二幕:逆变(Contravariance)——参数可以“更宽泛”

接下来,你给动物加个“吃饭”功能。

class Food {}
class AnimalFood extends Food {}abstract class Animal {public function eat(AnimalFood $food) {echo $this->name . " 吃 " . get_class($food);}
}

所有动物都吃“动物粮”(AnimalFood)。

但狗比较不挑食,它说:“我连普通食物都能吃!”

于是你重写狗的方法:

class Dog extends Animal {public function eat(Food $food) {  // 参数变宽了!echo $this->name . " 吃 " . get_class($food);}
}

父类要求传 AnimalFood,子类却接受更宽泛的 Food

❓这合法吗?
也合法!这就是“逆变”

✅ 逆变的核心思想:

参数可以变得更“宽泛”

就像你去吃饭,菜单写“本店只接受现金”。
但店长说:“其实刷卡、支付宝我们也收。”
👉 更包容了,没问题!

🔍 技术上:FoodAnimalFood 的父类,范围更广。狗能吃的东西更多,说明它更“包容”,不会破坏原有规则。

这是 逆变(Contravariance)
逆 = 相反 —— 继承是“子类 → 父类”,但参数类型却从“子类”变回“父类”,方向相反。

注意:部分逆变支持是从 PHP 7.2 开始的,但完整的逆变支持也是从 PHP 7.4 开始的。


🧩 第三幕:属性的“读写困境”

以前,PHP 的属性是“死板”的:

class Parent {public Animal $pet;
}class Child extends Parent {public Dog $pet; // ❌ 不行!类型不能变
}

因为属性既要“读”又要“写”:

  • “读”希望返回更具体的类型(协变)
  • “写”希望接受更宽泛的类型(逆变)

两者冲突,所以只能“不变”。

但从 PHP 8.4 开始,我们可以定义“只读”或“只写”属性!

interface PetOwner {public Animal $pet { get; } // 只读
}class DogOwner implements PetOwner {public Dog $pet; // ✅ 可以!只读 → 协变成立
}

因为只允许“读”,所以返回更具体的 Dog 是安全的。

✅ 只读 → 协变
✅ 只写 → 逆变
❌ 可读可写 → 不变


📝 总结:一张表看懂协变与逆变

场景能不能变?如何变?生活例子支持版本
返回值✅ 协变越来越具体(Animal → Cat)“动物” → “猫”PHP 7.4+
参数✅ 逆变越来越宽泛(AnimalFood → Food)“只能吃动物粮” → “啥都能吃”PHP 7.4+ (部分支持从 PHP 7.2 开始)
属性(只读)✅ 协变可以更具体“宠物” → “狗”PHP 8.4+
属性(可读可写)❌ 不变类型不能变既要读又要写,不能乱改-

💡 为什么要有协变和逆变?

为了让代码更灵活安全

  • 协变让你能返回更具体的对象,便于后续调用具体方法。
  • 逆变让你的子类更包容,适应更多输入。
  • 它们共同保证:子类不会破坏父类的契约

🎉 结语

协变与逆变,听起来高深,其实很简单:

  • 协变:返回值 → 越来越“小”(具体)
  • 逆变:参数 → 越来越“大”(宽泛)

记住这个口诀:

🔤 “出(返回)要具体,入(参数)要包容”

从 PHP 7.4 开始,这些特性让你的面向对象编程更加优雅、类型更安全。

现在,你已经不是“听不懂协变逆变”的人了,而是那个能讲清楚的人!👏


📌 适合读者:PHP 初学者、中级开发者、想理解类型系统的你
📅 适用版本:PHP 7.4+(逆变从 7.2 开始部分支持,7.4 完整支持)

http://www.dtcms.com/a/327345.html

相关文章:

  • Gradle(二)Gradle的优势、项目结构介绍
  • 电商双11美妆数据分析(一)
  • Honeywell霍尼韦尔A205压力传感器HC41H106P060169419G固瑞克117764美国制造
  • Rust 项目编译故障排查:从 ‘onnxruntime‘ 链接失败到 ‘#![feature]‘ 工具链不兼容错误
  • KAQG:一种用于生成难度可控问题的知识图谱的增强的RAG系统(论文大白话)
  • 2025AI行业升级生态战:谁在“种树”?谁在“造林”?
  • 02-Ansible 基本使用
  • Visual Studio中VC++目录、C/C++和链接器配置的区别与最佳实践
  • Minst手写数字识别
  • python2操作neo4j
  • 非凸科技受邀参加Community Over Code Asia 2025 Rust分论坛
  • 上海AI实验室发布MinerU2:通专融合路线如何补齐AI-Ready数据的最后一公里
  • AutoAgent节点入门:解锁智能体的自主规划能力
  • Myqsl建立库表练习
  • 盲盒抽谷机小程序系统开发:解锁盲盒新玩法,开启潮玩社交新时代
  • 论答题pk小程序软件版权的
  • DeepSeek-R1与RAGflow本地部署全流程指南:从模型下载到个人知识库构建实战
  • 真实案例 | 如何用iFlyCode开发Webpack插件?
  • string 类运算符重载
  • LeetCode Day5 -- 栈、队列、堆
  • JavaScript 实现模块懒加载的几种方式
  • 如何轻松解除Facebook封锁
  • flinksql bug: Received resultset tuples, but no field str
  • 阿里云国际DDoS高防:添加网站配置指南
  • 腾讯codebuddy.ai 安装实测【从零开始开发在线五子棋游戏:完整开发记录】
  • 机械学习--TF-IDF实战--红楼梦数据处理
  • wordpress数据库导入时的#1044错误
  • Linux中使用计划任务和tar命令实现文件备份
  • 【Unity】Spine重新播放动画时会闪烁上次动画的残影
  • K8S 节点初始化一键脚本(禁用 SELinux + 关闭 swap + 开启 ipvs 亲测实用)