## 什么是继承?
这个继承词已经被传了这么多,我最好的赌注是你已经在某种程度上弄清楚了它的意思。但是,让我们来看看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