## 什么是繼承?
這個繼承詞已經被傳了這么多,我最好的賭注是你已經在某種程度上弄清楚了它的意思。但是,讓我們來看看OOP中繼承的正式定義。繼承是一種允許類繼承另一個類的屬性和方法的機制。繼承另一個類的屬性和方法的類稱為子類,繼承的類稱為父類。
繼承允許我們在現有類的基礎上構建類,允許我們重用代碼并減少我們編寫的代碼量。正如我在本系列之前所指出的,OOP的靈感來自于人類如何思考和與周圍環境互動。繼承也是基于這個模型。我們周圍的很多物體都是其他物體的特殊化。例如,汽車是交通工具的特化,狗是動物的特化,等等,交通工具和動物有這么多,但它們都有一些共同的屬性和方法。單獨定義每個類中的屬性和方法完全是浪費時間。我們可以做的是將所有公共屬性和方法提取到一個單獨的類中,然后創建其他單獨的類來擴展該基類。
## PHP OOP中的繼承
在PHP OOP中實現繼承非常簡單--使用`extends`關鍵字。我們來看一個例子:
```php
class Animal
{
/**
* 構造函數,用于對象初始化
*
* @param string $name 設置對象的名稱屬性
* @param int $age 設置對象的年齡屬性
* @param string $color 設置對象的顏色屬性
*/
public function __construct(
public string $name,
public int $age,
public string $color
)
{
}
/**
* 本函數通過輸出字符串模擬對象進餐的動作,使用的名稱是對象初始化時提供的名稱
* 選擇直接使用echo進行輸出而不是返回值,是因為這里強調的是動作本身而非返回結果
*/
public function eat(): void
{
echo $this->name . " is eating";
}
/**
* 該方法直接在對象的上下文中使用,用于顯示對象正在睡覺的信息
* 它主要用于演示或調試目的,提供了一種簡單的方法來標識對象狀態
*/
public function sleep(): void
{
echo $this->name . " is sleeping";
}
}
```
這個`Animal`類定義了所有動物通用的屬性和方法。現在,讓我們創建一個繼承自`Animal`類的`Dog`和`Cat`類。
```
class Dog extends Animal {
public function bark() : void {
echo $this->name . " is barking";
}
}
class Cat extends Animal {
public function meow() : void {
echo $this->name . " is meowing";
}
}
$dog = new Dog("Rex", 5, "Brown");
$dog->eat(); // Rex is eating
$dog->bark(); // Rex is barking
$dog->sleep(); // Rex is sleeping
$cat = new Cat("Tom", 3, "White");
$cat->eat(); // Tom is eating
$cat->meow(); // Tom is meowing
$cat->sleep(); // Tom is sleeping
```
Dog和Cat類繼承自Animal類,以及它所附帶的所有內容(屬性和方法)。它們也有自己獨特的屬性和方法。`狗叫()`,貓`叫()`。
從示例中可以清楚地看到,在PHP中,多個類可以從一個類繼承。這就是所謂的*多級繼承*。一個子類也可以有它自己子類,所以我們基本上可以有一個類的家譜。這就是所謂的*分層繼承*。Eg.`Animal`類是`Dog`和`Cat`類的父類,`Dog`類是`GermanShepherd`和`Bulldog`類的父類。
```
class GermanShepherd extends Dog {
public function __construct(
public string $name,
public int $age,
public string $color
)
{
}
public function guard() : void{
echo $this->name . " is guarding";
}
}
class Bulldog extends Dog {
public function __construct(
public string $name,
public int $age,
public string $color
)
{
}
public function guard() : void{
echo $this->name . " is guarding";
}
}
```
## 什么是多態性?
多態性與繼承密切相關。它允許將不同類的對象視為同一類的對象。它允許我們以不同的方式執行一個動作。它允許在不需要知道對象的確切類型的情況下調用方法。多態性是一個希臘詞,意思是“多種形式”。它是OOP的一個強大功能,允許我們編寫靈活和可重用的代碼。
讓我們用一個類比來說明這一點。假設我們有一個`Vehicle`類,它有兩個方法-`drive()`和`startEngine()`。假設我們有三個類-`Car`、`Motorcycle`和`Bicycle`,它們繼承自`Vehicle`類,因此它是方法。
```
class Vehicle {
public function startEngine() : void{
echo "Starting the engine of the vehicle.";
}
public function move() : void{
echo "Driving a vehicle";
}
}
class Car extends Vehicle {
public function startEngine() : void{
echo "Starting the engine of the car with a key.";
}
public function move(): void {
echo "Driving a car with 4 wheels";
}
}
class Motorcycle extends Vehicle {
public function startEngine() : void {
echo "Starting the engine of the motorcycle with a kick.";
}
public function move() : void {
echo "Driving a motorcycle with 2 wheels";
}
}
class Bicycle extends Vehicle {
public function startEngine() : void {
echo "Bicycles don't have engines. Just pedal.";
}
public function move() : void {
echo "Driving a bicycle with 2 wheels";
}
}
$car = new Car();
$car->startEngine(); // Starting the engine of the car with a key.
$car->move(); // Driving a car with 4 wheels
$motorcycle = new Motorcycle();
$motorcycle->startEngine(); // Starting the engine of the motorcycle with a kick.
$motorcycle->move(); // Driving a motorcycle with 2 wheels
$bicycle = new Bicycle();
$bicycle->startEngine(); // Bicycles don't have engines. Just pedal.
$bicycle->move(); // Driving a bicycle with 2 wheels
```
現在這里沒有什么新的東西可看。從那以后我們就一直在繼承遺產。現在讓我們看看多態性是如何發揮作用的。
```
class Player {
private Vehicle $vehicle;
public function setVehicle(Vehicle $vehicle) {
$this->vehicle = $vehicle;
}
public function drive() {
$this->vehicle->startEngine();
$this->vehicle->move();
}
}
$car = new Car();
$motorcycle = new Motorcycle();
$bicycle = new Bicycle();
$player = new Player();
$player->setVehicle($car);
$player->drive(); // Starting the engine of the car with a key. Driving a car with 4 wheels
$player->setVehicle($motorcycle);
$player->drive(); // Starting the engine of the motorcycle with a kick. Driving a motorcycle with 2 wheels
```
下面是發生的情況:`Player`類的`$vehicle`屬性和`setVehicle()`方法需要一`個Vehicle`類型。但是,我們能夠傳入`汽車`和`摩托車`對象。當我們調用`$vehicle`的`startEngine()`和`move()`方法時,額外的魔力就來了。`Player`類不需要知道它所處理的對象的確切類型。它只是調用方法,然后調用適當的方法。這就是多態性的作用。
## 方法覆蓋
使多態性成為可能的一件事是方法重寫。方法重寫是子類重寫其父類的方法的能力。在前面的例子中,`Car`、`Motorcycle`和`Bicycle`類都覆蓋`了Vehicle`類的`startEngine()`和`move()`方法。在某些語言中,默認情況下不會重寫方法。你必須顯式地指定你想要重寫一個方法。在PHP中,方法被默認覆蓋。
另外,有些語言支持方法重載。方法重載是指擁有多個同名但參數不同的方法的能力。PHP不支持方法重載。
這里有一個方法重載的例子,如果我們在PHP中有它的話,它會是什么樣子。
```
class Vehicle {
public function startEngine() {
echo "Starting the engine of the vehicle.";
}
public function move() {
echo "Driving a vehicle";
}
}
class Car extends Vehicle {
public function startEngine() {
echo "Starting the engine of the car with a key.";
}
public function startEngine(string $key) {
echo "Starting the engine of the car with a $key.";
}
public function move() {
echo "Driving a car with 4 wheels";
}
}
$car = new Car();
$car->startEngine(); // Starting the engine of the car with a key.
$car->startEngine('blue key'); // Starting the engine of the car with a blue key.
```
> 注:上面的代碼會拋出一個錯誤,因為PHP不支持方法重載。這只是癡心妄想!
### 協變和逆變
協變性和逆變性聽起來像是很大的術語,但相信我,它們并不像看起來那么復雜。
協方差是最容易理解的。它允許一個方法的返回類型在子類中是一個比父類中相同方法返回的類型更具體的類型。這是一個很大的問題,讓我們來看看一個例子。
```
class Vehicle {
public static function make(): Vehicle {
return new Vehicle();
}
}
class Car extends Vehicle {
public static function make(): Car {
return new Car();
}
}
```
`Vehicle`類的`make()`方法返回一個`Vehicle`類型。但是在`Car`類中,我們將返回類型專門化為`Car`。這就是協方差的作用。你可以閱讀[Liscov替換原理](https://en.wikipedia.org/wiki/Liskov_substitution_principle)來理解為什么這是可能的。這意味著子類的返回類型不能不具體。
另一方面,矛盾性稍微復雜一點。它使用方法參數而不是返回類型。它允許一個方法的參數類型在子類中是一個比父類中相同參數的類型更不特定的類型。使用我們`的Vehicle`和`Car`類,如果一個方法需要`Vehicle`,自然地,我們可以傳入一`個Car`對象。沒什么新鮮事我們以前見過也做過。但是,如果方法需要一`個Car`對象,我們可以傳入一個`Vehicle`對象嗎?這就是逆變出現的地方。讓我們看一個例子。
```
class Driver {
public function drive(Car $car) {
echo "Driving a car";
}
}
$driver = new Driver();
$driver->drive(new Car()); // Driving a car
```
現在,如果你試圖傳入一個`Vehicle`對象,你會得到一個錯誤。如果我們有一個從`Driver`繼承的類,那么Contraversion就開始起作用了。
```
class UberDriver extends Driver {
public function drive(Vehicle $vehicle) {
echo "Driving a vehicle";
}
}
$uberDriver = new UberDriver();
$uberDriver->drive(new Vehicle()); // Driving a vehicle
```
即使父類需要一`個Car`或它的任何子類,我們也可以覆蓋該方法,使其需要一個`Vehicle`對象。這就是作用中的逆變。一開始可能會有點困惑,但你會掌握竅門的。
### 操作符
在前面的`UberDriver`示例中,如果我們想檢查傳入的對象是否是`Car`對象,該怎么辦?我們不能使用`is_a()`函數,因為它將返回`false`,因為傳入的對象是`Vehicle`對象。這就是`instanceof`操作符的用武之地。它允許我們檢查一個對象是類的實例還是它的子類。所以我們可以這樣做:
```
class UberDriver extends Driver {
public function drive(Vehicle $vehicle) {
if($vehicle instanceof Car) {
echo "Driving a car";
} else {
echo "Driving a vehicle";
}
}
}
$uberDriver = new UberDriver();
$uberDriver->drive(new Vehicle()); // Driving a vehicle
$uberDriver->drive(new Car()); // Driving a car
```
`instanceof`檢查一個對象是類的實例還是它的子類。它不檢查一個對象是否是父類的實例。所以如果我們做這樣的事情:
```
$car = new Car();
$car instanceof Vehicle;
```
它應該給我們真實的,但類似于:
```
$vehicle = new Vehicle();
$vehicle instanceof Car;
```
## 抽象類和方法
讓我們在車輛類比的上下文中反映這一點。在真實的意義上,我們永遠不會有一輛車。我們將有一輛汽車,摩托車,自行車等,所以它沒有意義有一個`車輛`類。這就是**抽象**類和方法概念的由來。
抽象類是不能自己實例化的類(即不能從該類創建對象)。它們是用來繼承的。它們作為其他類的藍圖,類似于常規類,但有一個關鍵的區別:它們可以包含抽象方法,這些方法沒有實現。抽象方法是聲明但未實現的方法。它們應該在孩子的課堂上實施。抽象類也可以包含帶有實現的常規方法。因此,讓我們重構代碼以使用抽象類。
```
abstract class Vehicle {
abstract public function start(): void;
abstract public function stop(): void;
public function refuel(): void {
// Refueling process for vehicles
}
}
class Car extends Vehicle {
public function start(): void {
echo "Starting the engine of the car with a key.";
}
public function stop(): void {
echo "Stopping the engine of the car.";
}
}
class Motorcycle extends Vehicle {
public function start(): void {
echo "Starting the engine of the motorcycle with a kick.";
}
public function stop(): void {
echo "Stopping the engine of the motorcycle.";
}
}
class Bicycle extends Vehicle {
public function start(): void {
echo "Bicycles don't have engines. Just pedal.";
}
public function stop(): void {
echo "Stopping the bicycle.";
}
}
```
在這個抽象類中,我們將`start()`和`stop()`定義為抽象方法。這些方法沒有在抽象類中實現,但提供了一個公共接口,任何從`Vehicle`繼承的類都必須實現該接口。從`Vehicle`繼承的具體類必須實現`start()`和`stop()`方法(如果不實現,IDE將拋出錯誤)。`refuel()`方法是一個有實現的常規方法。如果需要的話,它可以在子類中被重寫。
這并不意味著我們不能將`Vehicle`作為返回類型或方法參數。我們仍然可以擁有它。只是不能實例化它。所以我們可以這樣做:
```
class Driver {
public function drive(Vehicle $vehicle) {
$vehicle->start();
$vehicle->move();
$vehicle->stop();
}
}
```
### 為什么要使用抽象類和方法?
是的,在某種程度上,使類抽象似乎是多余的。為什么不讓他們正常?我們使用抽象類和方法有幾個原因。抽象類和方法允許您創建其他類遵循的藍圖,在代碼庫中強制執行特定結構。`abstract`關鍵字還允許您防止類被實例化,從而保持嚴格、緊密和有組織-沒有機會在這方面出現錯誤。
## 最終類和方法
說到保持嚴格,`最后`一個關鍵字是另一種方式。`final`關鍵字防止類被繼承,防止方法被重寫。現在有很多關于是否使用`final`關鍵字的爭論。有些人說使用它是一個很好的做法,而另一些人則說不是。我們有一個叫做`unfinalize的`包,用來從包中的類中刪除`final`(實際上它有很多粉絲)。我不想偏袒任何一方。我只告訴你怎么用,然后你自己決定。
```
final class Car extends Vehicle {
public function start(): void {
echo "Starting the engine of the car with a key.";
}
public function stop(): void {
echo "Stopping the engine of the car.";
}
}
```
`汽車`類現在是最后一個類。它不能被繼承。如果你嘗試這樣做,你會得到一個錯誤。方法也是如此。如果你試圖覆蓋一個final方法,你會得到一個錯誤。
- 設計模式系列
- 工廠方法模式
- 序言
- Windows程序注冊為服務的工具WinSW
- 基礎
- 安裝
- 開發規范
- 目錄結構
- 配置
- 快速入門
- 架構
- 請求流程
- 架構總覽
- URL訪問
- 容器和依賴注入
- 中間件
- 事件
- 代碼層結構
- 四個層次
- 路由
- 控制器
- 請求
- 響應
- 數據庫
- MySQL實時同步數據到ES解決方案
- 阿里云DTS數據MySQL同步至Elasticsearch實戰
- PHP中的MySQL連接池
- PHP異步非阻塞MySQL客戶端連接池
- 模型
- 視圖
- 注解
- @SpringBootApplication(exclude={DataSourceAutoConfiguration.calss})
- @EnableFeignClients(basePackages = "com.wotu.feign")
- @EnableAspectJAutoProxy
- @EnableDiscoveryClient
- 錯誤和日志
- 異常處理
- 日志處理
- 調試
- 驗證
- 驗證器
- 驗證規則
- 擴展庫
- 附錄
- Spring框架知識體系詳解
- Maven
- Maven和Composer
- 構建Maven項目
- 實操課程
- 01.初識SpringBoot
- 第1章 Java Web發展史與學習Java的方法
- 第2章 環境與常見問題踩坑
- 第3章 springboot的路由與控制器
- 02.Java編程思想深度理論知識
- 第1章 Java編程思想總體
- 第2章 英雄聯盟的小案例理解Java中最為抽象的概念
- 第3章 徹底理解IOC、DI與DIP
- 03.Spring與SpringBoot理論篇
- 第1章 Spring與SpringBoot導學
- 第2章 Spring IOC的核心機制:實例化與注入
- 第3章 SpringBoot基本配置原理
- 04.SprinBoot的條件注解與配置
- 第1章 conditonal 條件注解
- 第2章 SpringBoot自動裝配解析
- 05.Java異常深度剖析
- 第1章 Java異常分類剖析與自定義異常
- 第2章 自動配置Url前綴
- 06.參數校驗機制與LomBok工具集的使用
- 第1章 LomBok工具集的使用
- 第2章 參數校驗機制以及自定義校驗
- 07.項目分層設計與JPA技術
- 第1章 項目分層原則與層與層的松耦合原則
- 第2章 數據庫設計、實體關系與查詢方案探討
- 第3章 JPA的關聯關系與規則查詢
- 08.ORM的概念與思維
- 第1章 ORM的概念與思維
- 第2章 Banner等相關業務
- 第3章 再談數據庫設計技巧與VO層對象的技巧
- 09.JPA的多種查詢規則
- 第1章 DozerBeanMapper的使用
- 第2章 詳解SKU的規格設計
- 第3章 通用泛型Converter
- 10.令牌與權限
- 第1章 通用泛型類與java泛型的思考
- 常見問題
- 微服務
- demo
- PHP中Self、Static和parent的區別
- Swoole-Cli
- 為什么要使用現代化PHP框架?
- 公眾號
- 一鍵部署微信公眾號Markdown編輯器(支持適配和主題設計)
- Autodesigner 2.0發布
- Luya 一個現代化PHP開發框架
- PHPZip - 創建、讀取和管理 ZIP 文件的簡單庫
- 吊打Golang的PHP界天花板webman壓測對比
- 簡潔而強大的 YAML 解析庫
- 推薦一個革命性的PHP測試框架:Kahlan
- ServBay下一代Web開發環境
- 基于Websocket和Canvas實現多人協作實時共享白板
- Apipost預執行腳本如何調用外部PHP語言
- 認證和授權的安全令牌 Bearer Token
- Laradock PHP 的 Docker 完整本地開發環境
- 高效接口防抖策略,確保數據安全,避免重復提交的終極解決方案!
- TIOBE 6月榜單:PHP穩步前行,編程語言生態的微妙變化
- Aho-Corasick字符串匹配算法的實現
- Redis鍵空間通知 Keyspace Notification 事件訂閱
- ServBay如何啟用并運行Webman項目
- 使用mpdf實現導出pdf文件功能
- Medoo 輕量級PHP數據庫框架
- 在PHP中編寫和運行單元測試
- 9 PHP運行時基準性能測試
- QR碼生成器在PHP中的源代碼
- 使用Gogs極易搭建的自助Git服務
- Gitea
- webman如何記錄SQL到日志?
- Sentry PHP: 實時監測并處理PHP應用程序中的錯誤
- Swoole v6 Alpha 版本已發布
- Proxypin
- Rust實現的Redis內存數據庫發布
- PHP 8.4.0 Alpha 1 測試版本發布
- 121
- Golang + Vue 開發的開源輕量 Linux 服務器運維管理面板
- 內網穿透 FRP VS Tailscale
- 新一代開源代碼托管平臺Gitea
- 微服務系列
- Nacos云原生配置中心介紹與使用
- 輕量級的開源高性能事件庫libevent
- 國密算法
- 國密算法(商用密碼)
- GmSSL 支持國密SM2/SM3/SM4/SM9/SSL 密碼工具箱
- GmSSL PHP 使用
- 數據庫
- SQLite數據庫的Web管理工具
- 阿里巴巴MySQL數據庫強制規范
- PHP
- PHP安全測試秘密武器 PHPGGC
- 使用declare(strict_types=1)來獲得更健壯的PHP代碼
- PHP中的魔術常量
- OSS 直傳阿里騰訊示例
- PHP源碼編譯安裝APCu擴展實現數據緩存
- BI性能DuckDB數據管理系統
- 為什么別人可以是架構師!而我卻不是?
- 密碼還在用 MD5 加鹽?不如試試 password_hash
- Elasticsearch 在電商領域的應用與實踐
- Cron 定時任務入門
- 如何動態設置定時任務!而不是寫死在Linux Crontab
- Elasticsearch的四種查詢方式,你知道多少?
- Meilisearch vs Elasticsearch
- OpenSearch vs Elasticsearch
- Emlog 輕量級開源博客及建站系統
- 現代化PHP原生協程引擎 PRipple
- 使用Zephir編寫C擴展將PHP源代碼編譯加密
- 如何將PHP源代碼編譯加密,同時保證代碼能正常的運行
- 為什么選擇Zephir給PHP編寫動態擴展庫?
- 使用 PHP + XlsWriter實現百萬級數據導入導出
- Rust編寫PHP擴展
- 阿里云盤開放平臺對接進行文件同步
- 如何構建自己的PHP靜態可執行文件
- IM后端架構
- RESTful設計方法和規范
- PHP編譯器BPC 7.3 發布,成功編譯ThinkPHP8
- 高性能的配置管理擴展 Yaconf
- PHP實現雪花算法庫 Snowflake
- PHP官方現代化核心加密庫Sodium
- pie
- 現代化、精簡、非阻塞PHP標準庫PSL
- PHP泛型和集合
- 手把手教你正確使用 Composer包管理
- JWT雙令牌認證實現無感Token自動續期
- 最先進PHP大模型深度學習庫TransformersPHP
- PHP如何啟用 FFI 擴展
- PHP超集語言PXP
- 低延遲雙向實時事件通信 Socket.IO
- PHP OOP中的繼承和多態
- 強大的現代PHP高級調試工具Kint
- PHP基金會
- 基于webman+vue3高質量中后臺框架SaiAdmin
- 開源免費的定時任務管理系統:Gocron
- 簡單強大OCR工具EasyOCR在PHP中使用
- PHP代碼抽象語法樹工具PHP AST Viewer
- MySQL數據庫管理工具PHPMyAdmin
- Rust編寫的一款高性能多人代碼編輯器Zed
- 超高性能PHP框架Workerman v5.0.0-beta.8 發布
- 高并發系列
- 入門介紹及安裝
- Lua腳本開發 Hello World
- 執行流程與階段詳解
- Nginx Lua API 接口開發
- Lua模塊開發
- OpenResty 高性能的正式原因
- 記一次查找 lua-resty-mysql 庫 insert_id 的 bug
- 包管理工具OPM和LuaRocks使用
- 異步非阻塞HTTP客戶端庫 lua-resty-http
- Nginx 內置綁定變量
- Redis協程網絡庫 lua-resty-redis
- 動態HTML渲染庫 lua-testy-template
- 單獨的
- StackBlitz在線開發環境
- AI
- 基礎概念
- 12312
- 基礎鏡像的坑
- 利用phpy實現 PHP 編寫 Vision Transformer (ViT) 模型
- 語義化版本 2.0.0