【[特殊字符][特殊字符] 協變與逆變:用“動物收容所”講清楚 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.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…