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

“为什么子类方法可以返回 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.pswp.cn/news/918537.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/918537.shtml
英文地址,请注明出处:http://en.pswp.cn/news/918537.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

cesium/resium 修改子模型材质

我是 www.v2ex.com/t/1151549 的作者,在csdn这边补全一些更多的信息 相关工具 主项目插件版本: "cesium": "^1.131.0",、"resium": "^1.19.0-beta.1"、"three": "^0.178.0"、"react…

nvm install 14.21.3 时npm 无法下载和识别

错误:C:\Users\H3C>nvm install 14.21.3 Downloading node.js version 14.21.3 (64-bit)... Complete Downloading npm... Creating C:\Users\H3C\AppData\Local\Temp\nvm-install-939491942\temp Downloading npm version 6.14.18... Error while downloading h…

【网络运维】Linux:LNMP 项目实践

LNMP 项目实践 简介:什么是 LAMP/LNMP LAMP:LinuxApacheMysql/MariadbPHP/Python/Perl。 LNMP:LinuxNginxMysql/MariadbPHP/Python/Perl。 Linux:操作系统,提供程序运行基础。Apache/Nginx:Web 服务器&…

用 Docker 安装并启动 MySQL:从零到实战的完整指南

用 Docker 安装并启动 MySQL:从零到实战的完整指南MySQL 是目前最流行的关系型数据库之一,广泛应用于各类应用系统中。使用 Docker 部署 MySQL 可以极大简化环境配置,保证开发、测试和生产环境的一致性。本文将详细介绍如何使用 Docker 安装、…

动态规划----1.爬楼梯

70. 爬楼梯 - 力扣(LeetCode) /** 1阶:1步,即1种; 2阶:1步1步或直接2步,即2种 f(1) 1,f(2) 2 3阶:由1阶迈2步,或2阶迈一步; 4阶:由2阶迈2步,或3阶1步; n阶:由n-2阶迈2步,或n-1阶迈1步 f(n) f(n - 1) f(n - 2) */ class Solution {/**1阶:1步,即1种…

special topic 11 (1)

preface 虽然我知道专业课必须得学,但是要学的东西,好多,我对专业课很害怕,稍微往后挪一挪,今天学了两个强化网课之后再学专业课吧。今天的目标是学完 11 到 14.任重道远,加油!从今天开始尽量早…

MTD和FTL的关系

在嵌入式存储系统里,MTD(Memory Technology Device)和 FTL(Flash Translation Layer)是上下两层、互相配合的概念。你可以把它想成**“原始硬件接口”和“硬盘驱动”**的关系。1. MTD 是什么定位:内核里对原…

自动驾驶 HIL 测试:构建 “以假乱真” 的实时数据注入系统

01 引言在端到端自动驾驶的研发竞赛中,算法的迭代速度远超物理世界的测试能力。单纯依赖路测不仅成本高昂、周期漫长,更无法穷尽决定系统安全性的关键边缘场景(Corner Cases)。因此,硬件在环(HIL&#xff0…

jdk升级

列出所有的jdk版本 /usr/libexec/java_home -V 永久切换版本 export JAVA_HOME(/usr/libexec/javahome−v11)exportPATH(/usr/libexec/java_home -v 11) export PATH(/usr/libexec/javah​ome−v11)exportPATHJAVA_HOME/bin:$PATH 保存后执行 source ~/.zshrc

Openlayers基础教程|从前端框架到GIS开发系列课程(24)openlayers结合canva绘制矩形绘制线

本章节讲解Canvas如何结合 Openlayer 使用&#xff0c;首先我们讲解Canvas的绘图基础。我们初始化地图的时候可以看见&#xff0c;实际上Openlayer的地图就是用Canvas实现绘制的。Canvas绘制基本概念什么是canvas&#xff1f;HTML5 <canvas> 元素用于图形的绘制&#…

深度学习——01 深度学习简介

1 什么是深度学习&#xff1f;人工智能是个大范畴&#xff0c;目标是打造智能机器和程序&#xff1b; 机器学习是实现人工智能的一种途径&#xff0c;它能让机器在不被明确编程的情况下自主学习&#xff1b;而深度学习&#xff0c;是机器学习的一个分支&#xff0c;它是基于深度…

自然语言处理( NLP)基础

一、基本概念自然语言处理也就是Natural Language Processing&#xff0c;简称NLP。NLP就是人工只能和语言学领域的一个分支&#xff0c;涉及到计算机与人类语言之间的相互作用。主要目标是让计算机能够理解、解释和生成人类语言的数据。1 自然语言处理的基本介绍NLP包括但不限…

云原生作业(nginx)

目录 1 Web 服务基础介绍 1.1 Web 服务介绍 1.1.1 Apache 经典的 Web 服务端 1.1.2 Nginx-高性能的 Web 服务端 1.1.3 用户访问体验和性能 1.1.4 服务端 I/O 流程 1.2 I/O 模型 1.2.1 I/O 模型相关概念 1.2.2 网络 I/O 模型 1.2.3 五种 IO 对比 1.2.4 I/O 的具体实现…

NY198NY203美光固态闪存NY215NY216

NY198NY203美光固态闪存NY215NY216技术架构与核心创新突破美光NY系列&#xff08;含NY198/NY203/NY215/NY216&#xff09;作为新一代企业级存储解决方案&#xff0c;其底层采用232层NAND闪存三维堆叠工艺&#xff0c;如同垂直建造数字世界的摩天大楼&#xff0c;在有限芯片面积…

後端開發技術教學(四) 數據交互延伸

書接上回&#xff1a;後端開發技術教學(三) 表單提交、數據處理-CSDN博客 必要資源&#xff1a; trae中文版下載網址: TRAE - The Real AI Engineer phpStudy 2018 : phpStudy - Windows 一键部署 PHP 开发环境 小皮出品 前言 大家好&#xff0c;我是小楓。書接上期說到的後…

华清远见25072班C语言学习day7

重点内容&#xff1a;二维整形数组&#xff1a;定义&#xff1a;数据类型 数组名[行数][列数];数组中元素的访问&#xff1a;通过行标和列标来访问、行标从0开始&#xff0c;列标从0开始初始化和赋值&#xff1a;int arr1[2][3]{1,2,3,4,5,6}; -->二维数组完全初始化int arr…

FPGA实现Aurora 64B66B数据回环传输,基于GTX高速收发器,提供2套工程源码和技术支持

目录1、前言Aurora 64B66B是啥&#xff1f;官方有Example&#xff0c;为何要用你这个&#xff1f;工程概述免责声明2、相关方案推荐我已有的所有工程源码总目录----方便你快速找到自己喜欢的项目我这里已有的 GT 高速接口解决方案本方案在Aurora 8B10B上的应用3、工程详细设计方…

实用硬件设计规范要点-原理图

此处所涉及的并非指导硬件工程师如何绘制原理图&#xff0c;而是旨在规范原理图的模板&#xff0c;统一原理图的设计方式&#xff0c;确保原理图具备良好的可阅读性&#xff0c;并统一与下一环节——PCB LAYOUT的接口。具体的硬件设计方法&#xff0c;诸位需在未来的工作中加以…

用 Apache Iceberg 与 Apache Spark 在 Google Cloud 打造高性能、可扩展的数据湖仓

大数据分析版图不断演进&#xff0c;组织正寻求更灵活、可扩展且具成本效益的方式来管理与分析海量数据。这一追求催生了数据湖仓范式&#xff0c;它将数据湖的低成本存储与灵活性&#xff0c;与数据仓库的数据管理能力和事务一致性相结合。站在这场变革中心的是 Apache Iceber…

【PyTorch学习笔记 - 02】 Datasets DataLoaders

前言 处理数据样本的代码可能会变得杂乱无章且难以维护&#xff1b;为了获得更好的可读性和模块化&#xff0c;我们理想的情况是将数据集代码与模型训练代码解耦。PyTorch 提供了两个数据处理类&#xff1a; torch.utils.data.DataLoader 和 torch.utils.data.Dataset&#x…