PHP OOP 面向对象进阶 27 个问题让你充分了解对象特性

这里整理了一些 PHP 面向对象编程中容易搞混的知识点,很多都是面试常考题。不过学这些不只是为了应付面试,更重要的是真正搞懂面向对象编程的原理。

原文链接 - PHP OOP 面向对象进阶 27 个问题让你充分了解对象特性

PHP 中的魔术方法是什么?

魔术方法就是那些以双下划线(__)开头的特殊方法,在特定情况下 PHP 会自动调用它们。

这些方法是 PHP 内置的,可以让你控制对象在各种情况下的行为。

常用的魔术方法:

  • __construct() → 创建对象时自动执行
  • __destruct() → 对象销毁时自动执行
  • __get($name) → 读取不存在的属性时调用
  • __set($name, $value) → 设置不存在的属性时调用
  • __isset($name) → 对不存在的属性用 isset()empty() 时调用
  • __unset($name) → 对不存在的属性用 unset() 时调用
  • __call($method, $args) → 调用不存在的方法时触发
  • __callStatic($method, $args) → 调用不存在的静态方法时触发
  • __toString() → 对象转字符串时调用
  • __invoke() → 把对象当函数用时调用
  • __clone() → 克隆对象时执行

什么是构造函数?有哪些类型?

PHP 中的构造函数是一个特殊方法(__construct),在从类创建对象时自动执行。

  • 用于初始化属性或执行设置代码
  • 使用 __construct() 定义

构造函数类型

1- 默认构造函数

如果你没有定义构造函数,系统会自动创建一个。

class A
{
}
$obj = new A(); // 即使没有 __construct 也能正常工作

2- 参数化构造函数

创建对象时接受参数。

class B
{
public function __construct($name)
{
echo "Hello $name";
}
}
new B("John"); // Hello John

3- 拷贝构造函数(PHP 中的模拟实现)

PHP 没有像 C++ 那样真正的拷贝构造函数,但你可以通过向构造函数传递对象来模拟。

class C
{
public $x;
public function __construct($obj = null) {
if ($obj) $this->x = $obj->x;
}
}

PHP 中构造函数可以是私有的吗?

可以,在 PHP 中,构造函数可以是私有的。

为什么要这样做?

  • 限制从类外部创建对象
  • 常用于单例模式
class Singleton {
private static $instance; private function __construct() {
echo "私有构造函数\n";
} public static function getInstance() {
if (!self::$instance) {
self::$instance = new Singleton();
}
return self::$instance;
}
} $obj1 = Singleton::getInstance(); // 正常工作
$obj2 = new Singleton(); // 错误:无法访问私有构造函数

PHP 中可以创建抽象类的实例吗?

不可以,你无法直接从抽象类创建实例。必须用子类继承它,然后从子类创建对象。

abstract class Animal {
abstract public function sound();
} // $a = new Animal(); // 错误 class Dog extends Animal {
public function sound() {
return "汪汪";
}
} $d = new Dog(); // 正常工作
echo $d->sound(); // 汪汪

原因是抽象类是不完整的。

  • 它可能包含抽象方法(声明但未实现)
  • PHP 不知道如何创建一个完整的对象,如果某些方法缺失的话
  • 这就是为什么必须在子类中继承并提供实现,然后才能实例化

PHP 中可以创建接口的实例吗?

不可以,接口只是一个契约;你必须在类中实现它,然后从该类创建对象。

  • 它定义了必须做什么,而不是如何做
  • 没有具体的方法体,PHP 无法创建可用的对象
  • 你需要一个实现接口的类来提供实际代码,然后实例化该类

简而言之:接口 = 契约,不是对象。

PHP 中接口可以继承多个接口吗?

可以。PHP 允许接口继承多个接口(接口的多重继承)。

interface CanDrive {
public function drive();
} interface CanFly {
public function fly();
} interface FlyingCar extends CanDrive, CanFly {} class MyFlyingCar implements FlyingCar {
public function drive() { echo "正在驾驶\n"; }
public function fly() { echo "正在飞行\n"; }
} $car = new MyFlyingCar();
$car->drive(); // 正在驾驶
$car->fly(); // 正在飞行

PHP 中的构造函数属性提升是什么?

在 PHP(从 PHP 8.0 开始),构造函数属性提升是一种简写方式,让你可以直接在构造函数中声明和初始化类属性。

这减少了样板代码。

  • 需要可见性修饰符(public、protected、private)
  • 支持类型声明、默认值等

之前(没有属性提升):

class User {
private string $name;
private int $age; public function __construct(string $name, int $age) {
$this->name = $name;
$this->age = $age;
}
}

之后(使用属性提升):

class User {
public function __construct(
private string $name,
private int $age
) {}
}

PHP 中的析构函数是什么?

PHP 拥有与其他面向对象语言(如 C++)类似的析构函数概念。一旦不再有对特定对象的其他引用,或者在关闭序列期间以任何顺序,析构函数方法就会被调用。

PHP 中的析构函数是一个名为 __destruct() 的特殊方法,当对象被销毁或超出作用域时自动执行。

要点:

  • 用于清理任务(关闭数据库连接、释放资源等)
  • 每个类只能有一个析构函数
  • 自动调用,不允许参数
class Test {
public function __construct() {
echo "对象已创建\n";
} public function __destruct() {
echo "对象已销毁\n";
}
} $t = new Test(); // 对象已创建
// 当脚本结束或 $t 被 unset 时 → 对象已销毁

简而言之__construct() = 设置,__destruct() = 清理。

你对 PHP 中的 final 关键字了解多少?

在 PHP 中,final 关键字用于防止重写或继承。

Final 类 → 不能被继承。

final class Base {
public function sayHello() {
echo "Hello";
}
} class Child extends Base {} // 错误:无法继承 final 类

Final 方法 → 不能在子类中被重写。

class Base {
final public function sayHello() {
echo "Hello";
}
} class Child extends Base {
public function sayHello() {} // 错误:无法重写 final 方法
}

你能解释一下 PHP 中 final 和 abstract 在类和方法方面的区别吗?

final

  • 防止类的继承
  • 防止方法的重写
  • final 类可以被实例化
  • final 方法必须有方法体(实现)
  • 用于锁定行为

abstract

  • 强制继承(子类必须继承)
  • 强制实现抽象方法
  • 抽象类不能直接实例化
  • 抽象方法没有方法体
  • 用于定义蓝图

记忆技巧:

  • final = 不能再改变
  • abstract = 必须稍后完成

什么是静态方法?

PHP 中的静态方法是属于类本身而不是类实例的方法。

  • 使用 static 关键字声明
  • 使用 ClassName::methodName() 调用,而不是 $object->methodName()(因为 $this 需要对象)
  • 不能被子类重写,因为它们属于类本身
class Math {
public static function add($a, $b) {
return $a + $b;
}
} echo Math::add(5, 3); // 8

如果父类定义一个方法为非静态,子类可以重写并声明为静态吗(或相反)?

不可以,在 PHP 中,如果在子类中重写方法,其静态/非静态性质必须与父类匹配。

class ParentClass {
public function show() {}
} class ChildClass extends ParentClass {
public static function show() {} // 致命错误
} // 致命错误:无法将非静态方法 ParentClass::show() 在类 ChildClass 中设为静态

self:: vs static:: vs parent:: 的区别?

self::

  • 指向编写代码的当前类
  • 不考虑继承
class A {
public static function who() { echo "A"; }
public static function test() { self::who(); }
}
class B extends A {
public static function who() { echo "B"; }
}
B::test(); // 输出 "A"

static::(后期静态绑定)

  • 类似于 self::,但尊重继承
  • 使用运行时实际调用的类
class A {
public static function who() { echo "A"; }
public static function test() { static::who(); }
}
class B extends A {
public static function who() { echo "B"; }
}
B::test(); // 输出 "B"

parent::

  • 用于调用父类的方法或构造函数
class A {
public function greet() { echo "来自 A 的问候"; }
}
class B extends A {
public function greet() {
parent::greet(); // 调用父类方法
echo " 以及来自 B 的问候";
}
}
$obj = new B();
$obj->greet(); // 来自 A 的问候 以及来自 B 的问候

总结:

  • self:: → 始终是当前类
  • static:: → 后期绑定,运行时类
  • parent:: → 明确指向父类

PHP 中 $this vs self 的区别?

$this

  • 指向当前对象实例
  • 用于访问实例属性/方法
class User {
public $name = "Ali";
public function getName() {
return $this->name; // 指向当前对象
}
}
$user = new User();
echo $user->getName(); // Ali

self

  • 指向当前类本身(不是实例)
  • 用于静态方法/属性或常量
  • 不使用后期静态绑定(始终指向编写它的类)
class User {
const ROLE = "会员";
public static function getRole() {
return self::ROLE; // 指向 User::ROLE
}
}
echo User::getRole(); // 会员

$this → 对象 → 实例上下文

self → 类 → 静态上下文

PHP 中后期静态绑定是如何工作的?

PHP 中的后期静态绑定(LSB)是关于 static::self:: 的工作方式对比。

核心思想

  • self:: → 在编译时解析(编写它的类)
  • static:: → 在运行时解析(实际调用的类)

这就是为什么称为后期——PHP 只在调用方法时才决定使用哪个类。

简而言之:

  • self:: = 坚持当前类
  • static:: = 使用实际调用方法的类(后期绑定)

如果两个 trait 定义了相同的方法会发生什么?

如果两个 trait 定义了相同的方法,PHP 会抛出致命错误,除非你明确解决冲突。

你可以在类内部使用 insteadofas 来解决冲突。

trait A {
public function sayHello() { echo "来自 A 的问候"; }
}
trait B {
public function sayHello() { echo "来自 B 的问候"; }
} class Test {
use A, B {
A::sayHello insteadof B; // 使用 A 的版本
B::sayHello as sayHelloB; // 给 B 的版本起别名
}
} $obj = new Test();
$obj->sayHello(); // 来自 A 的问候
$obj->sayHelloB(); // 来自 B 的问候

简而言之:

  • 冲突 = 致命错误
  • 使用 insteadof 或 as 解决

如果两个 trait 定义了相同的属性会发生什么?

如果两个 trait 定义了相同的属性,PHP 会抛出致命错误,因为它无法决定使用哪一个。

示例:

trait A {
public $x = 1;
}
trait B {
public $x = 2; // 冲突
} class Test {
use A, B; // 致命错误:Trait 属性冲突
}

与方法不同(可以使用 insteadofas 解决),属性冲突无法解决——你必须重命名或删除其中一个。

trait 可以实现接口吗?

不可以。

  • trait 不是类,它只是代码复用工具(方法/属性的集合)
  • 只有类可以实现接口
  • 但是,trait 可以定义恰好满足接口契约的方法,但它不能正式声明 implements

简而言之:Trait 不能实现接口,只有类可以。

接口和抽象类的区别是什么?何时使用?

接口

  • 只能声明方法(没有实现,除了常量外没有属性)
  • 一个类可以实现多个接口(多重继承)
  • 用于定义契约(必须做什么)

抽象类

  • 可以声明抽象方法(没有方法体)和普通方法(有方法体)
  • 可以有属性和常量
  • 一个类只能继承一个抽象类
  • 用于需要基类共享代码 + 强制某些方法的场景

何时使用:

  • 接口 → 如果你只需要契约(不同的类必须实现某些方法)
  • 抽象类 → 如果你需要有共享代码的基类 + 部分抽象

PHP 中组合 vs 继承?

继承("是一个"关系)

  • 一个类继承另一个类
  • 子类获得父类的属性和方法
  • 强耦合 → 如果父类改变,子类可能会出问题
class Vehicle {
public function move()
{
echo "移动中";
}
}
class Car extends Vehicle {} // Car *是一个* Vehicle
$car = new Car();
$car->move(); // 移动中

组合("有一个"关系)

  • 一个类包含另一个类作为属性
  • 委托工作而不是继承
  • 更灵活,耦合度更低
class Engine {
public function start()
{
echo "引擎启动";
}
}
class Car {
private $engine;
public function __construct()
{
$this->engine = new Engine();
}
public function drive() { $this->engine->start(); echo " & 正在驾驶"; }
} $car = new Car();
$car->drive(); // 引擎启动 & 正在驾驶

⚖️ 何时使用

  • 继承 → 当子类确实是父类的一种类型时使用(清晰的层次结构)
  • 组合 → 当类具有来自另一个类的行为/部分时使用(在大多数情况下更灵活)

经验法则:优先使用组合而不是继承(常见的设计原则)。

组合优于继承原则

组合优于继承原则建议尽可能使用组合而不是继承。原因是虽然继承允许你根据另一个类来定义一个类的行为,但它将父类的生命周期和访问级别绑定到子类,这可能使代码更加僵化。

组合允许对象通过关系而不是通过僵化的类结构来获得行为和特性,从而实现更好的模块化和类之间更低的耦合。

子类可以降低继承方法的可见性吗(例如,public → protected)?

不可以,子类不能降低继承方法的可见性。

如果方法在父类中是 public,在子类中必须保持 public。

可见性只能扩大(例如,protected → public),但绝不能限制(public → protected/private)。

降低可见性

class ParentClass {
public function showMessage() {
echo "来自父类的消息";
}
} class ChildClass extends ParentClass {
// 这会导致致命错误
protected function showMessage() {
echo "来自子类的消息";
}
}
// 错误 → ChildClass::showMessage() 的访问级别
// 必须是 public(如同类 ParentClass 中一样)

扩大可见性

class ParentClass {
protected function showMessage() {
echo "来自父类的消息";
}
} class ChildClass extends ParentClass {
// 允许:可见性扩大了
public function showMessage() {
echo "来自子类的消息";
}
}

PHP OOP 中数据隐藏 vs 封装?

数据隐藏

数据隐藏是封装的基本原则。它涉及限制对对象内部状态的直接访问。通过将数据成员(属性)设为私有,我们防止外部代码直接修改它们。这种保护确保数据完整性并防止意外后果。

  • 概念:限制对类数据的直接访问
  • 在 PHP 中实现:使用可见性(private、protected)

示例:

class User {
private $password; // 对外部隐藏
}

封装

将数据(属性)和操作数据的方法(函数)捆绑在一个单元(类)中的概念。它限制对对象某些组件的直接访问,有助于防止对方法和数据的意外干扰和误用。

  • 概念:将数据 + 行为包装在一起,通过方法控制访问
  • 在 PHP 中实现:使用 getter/setter 或受控方法

示例:

class User {
private $password;
public function setPassword($pwd)
{
$this->password = $pwd;
}
public function getPassword()
{
return $this->password;
}
}

简而言之:

  • 数据隐藏 = 防止直接访问
  • 封装 = 通过方法进行受控访问

PHP OOP 中动态 vs 静态多态?

动态多态(运行时)

函数的实际实现在运行时或执行期间决定。(方法重写)

  • 通过方法重写实现(子类重写父类方法)
  • 调用哪个方法的决定发生在运行时
class Animal {
public function speak()
{
echo "动物叫声";
}
}
class Dog extends Animal {
public function speak()
{
echo "汪汪";
}
} $pet = new Dog();
$pet->speak(); // "汪汪"(运行时决定)

静态多态(编译时)

函数的实际实现在编译时决定。(方法重载)

  • PHP 不支持真正的编译时多态(如 C++ 方法重载)
  • 但你可以通过以下方式模拟:
    • 通过 __call() / __callStatic() 魔术方法进行方法重载
    • 默认参数或类型检查
class Calculator {
public function add($a, $b, $c = 0) {
return $a + $b + $c;
}
} $calc = new Calculator();
echo $calc->add(2, 3); // 5
echo $calc->add(2, 3, 4); // 9

PHP OOP 中的协变和逆变?

协变

子类方法可以返回比父类更具体(更窄)的类型。

class Animal {}
class Dog extends Animal {} class ParentClass {
public function getAnimal(): Animal {}
} class ChildClass extends ParentClass {
public function getAnimal(): Dog {} // 协变返回类型
}

逆变

子类方法可以接受比父类更不具体(更宽)的参数类型。

class Animal {}
class Dog extends Animal {} class ParentClass {
public function setAnimal(Dog $dog) {}
} class ChildClass extends ParentClass {
public function setAnimal(Animal $animal) {} // 逆变参数
}

协变:允许子类的方法返回比其父类方法的返回类型更具体的类型。

逆变:允许参数类型在子方法中比其父类中的参数类型更不具体。

注意:PHP 中的协变(返回类型)和逆变(参数类型)直接关系到里氏替换原则(LSP)。它们确保子类可以安全地替换父类而不破坏程序。

PHP 中 final 属性和 const 的区别?

final 属性(从 PHP 8.1 开始)

  • 意味着该属性不能在子类中被重写
  • 但其值仍然可以在运行时改变
class A
{
final public string $name = "Ali";
} class B extends A
{
public string $name = "Omar"; // 致命错误(不能重写 final 属性)
}

const

  • 类常量,用 const 定义
  • 不可变 → 其值在运行时不能改变
  • 通过 ClassName::CONST_NAME 访问
class A
{
public const VERSION = "1.0";
} echo A::VERSION; // 1.0
A::VERSION = "2.0"; // 错误(不能重新赋值常量)

如果私有属性仍然可以通过 getter 和 setter 访问,这与直接将属性设为 public 有什么区别?

私有属性不能从类外部直接访问。

class User {
private $name = "Ali";
} $u = new User();
echo $u->name; // 错误:无法访问私有属性

要允许受控访问,你使用 getter/setter 方法:

class User {
private $name; public function __construct($name) {
$this->name = $name;
} public function getName() {
return $this->name;
} public function setName($name) {
$this->name = $name;
}
} $u = new User("Ali");
echo $u->getName(); // Ali
$u->setName("Omar");
echo $u->getName(); // Omar

区别

直接访问(public 属性):任何人都可以自由更改值 → 没有控制。

Getter/Setter(private 属性):你控制属性如何被读取或修改。例如,你可以添加验证、日志记录或使其只读。

所以重点是:使用 getter/setter,你添加了封装 → 控制、验证和灵活性。

public function setName($name) {
if (strlen($name) < 3) {
throw new Exception("姓名太短");
}
$this->name = $name;
}

OOP 总是好的吗?还是有一些缺点?

OOP 范式的缺点

增加复杂性 — 设计类、继承和抽象对于小型/简单项目来说可能是过度设计。

执行速度较慢 — 对象创建和方法调用相比过程式代码增加了开销。

更多内存使用 — 对象存储元数据,可能比简单函数消耗更多内存。

学习曲线更陡峭 — 需要理解 OOP 原则(继承、多态、封装)。

紧耦合风险 — 糟糕的 OOP 设计可能导致僵化、难以更改的代码。

并非总是必需 — 对于小脚本,过程式 PHP 更快更简单。

PHP OOP 面向对象进阶 27 个问题让你充分了解对象特性的更多相关文章

  1. day26、面向对象进阶:多态、封装、反射

    一.多态 什么是多态: 类的继承有两层意义:1.改变 2.扩展 多态就是类的这两层意义的一个具体的实现机. 即:调用不同类实例化的对象,下的相同的方法,实现的过程不一样 python中的标准类型就是多 ...

  2. python_面向对象进阶(7)

    第1章 面向对象特性—继承(补充) 1.1 接口类.抽象类介绍 1.2 接口类 1.3 接口类应用过程 1.3.1 第一版:完成多种支付方式接口 1.3.2 第二版: 归一化设计,统一支付方式 1.3 ...

  3. Python全栈开发【面向对象进阶】

    Python全栈开发[面向对象进阶] 本节内容: isinstance(obj,cls)和issubclass(sub,super) 反射 __setattr__,__delattr__,__geta ...

  4. Python开发【第七篇】:面向对象 和 python面向对象进阶篇(下)

    Python开发[第七篇]:面向对象   详见:<Python之路[第五篇]:面向对象及相关> python 面向对象(进阶篇)   上一篇<Python 面向对象(初级篇)> ...

  5. OOP面向对象三大特点

    OOP面向对象三大特点 (一)封装:将现实中一个事物的属性和功能集中定义在一个对象中.(创建对象) 创建对象的3种方式: 1.直接量方式:(创建一个单独的对象) var obj={ 属性名:值,    ...

  6. python基础——面向对象进阶下

    python基础--面向对象进阶下 1 __setitem__,__getitem,__delitem__ 把对象操作属性模拟成字典的格式 想对比__getattr__(), __setattr__( ...

  7. python基础——面向对象进阶

    python基础--面向对象进阶 1.isinstance(obj,cls)和issubclass(sub,super) isinstance(obj,cls)检查是否obj是否是类 cls 的对象 ...

  8. 周末班:Python基础之面向对象进阶

    面向对象进阶 类型判断 issubclass 首先,我们先看issubclass() 这个内置函数可以帮我们判断x类是否是y类型的子类. class Base: pass class Foo(Base ...

  9. Python面向对象进阶和socket网络编程-day08

    写在前面 上课第八天,打卡: 为什么坚持?想一想当初: 一.面向对象进阶 - 1.反射补充 - 通过字符串去操作一个对象的属性,称之为反射: - 示例1: class Chinese: def __i ...

  10. 铁乐学python_day24_面向对象进阶1_内置方法

    铁乐学python_day24_面向对象进阶1_内置方法 题外话1: 学习方法[wwwh] what where why how 是什么,用在哪里,为什么,怎么用 学习到一个新知识点的时候,多问问上面 ...

随机推荐

  1. Java MCP 实战:构建跨进程与远程的工具服务

    一.MCP 协议简介 MCP(Model Context Protocol,模型上下文协议)是由Anthropic推出的一种开放标准协议,旨在为大语言模型(LLM)与外部数据源.工具和服务提供标准化. ...

  2. 前端开发系列075-JQuery篇之框架源码解读[结构]

    这篇文章将主要介绍jQuery框架的前面几百行代码并说明jQuery框架的整体结构. 一.源码解读 这里先简单贴出jQuery框架3.3.1版本中的前600行代码,其它和整体结构无关的部分省略了. * ...

  3. Extraction of the Quad Layout of a Triangle Mesh Guided by Its Curve Skeleton

    简介 论文简单构述了一个主题,输入三角网格,根据三角网格的骨架信息得到四边形网格 流程步骤 TIPS: 注意红色节点是分支节点和边节点,但是之间其实还是有一部分的折线,逻辑上也是一种节点.这种节点采用 ...

  4. USB.org + USB 3.0 Type-C + PD(Power Delivery)240W

    www.usb.org: USB.org Document Library USB Charger (USB Power Delivery) | USB-IF Type-C USB Type-C Ca ...

  5. 高等数值分析(高性能计算,并行计算) (Parallel and High Performance Computing)

    https://github.com/OpenMP https://math.ecnu.edu.cn/~jypan/Teaching/ParaComp/ Parallel and High Perfo ...

  6. GStreamer开发笔记(九):gst-rtcp-server安装和部署实现简单的rtsp-server服务器推流Demo

    前言   Gstreamer还有一个重要的功能就是充当rtsp流媒体服务器.   注意   本ubuntu是虚拟机,对延迟可能影响较大,延迟可作为参考,物理机可能更快.   Demo      RTP ...

  7. @Value("${dbpc.path}")和@Value("#{dbpc.path}")区别

    这两个注解都可以用来将值注入到Java Bean的字段中.但是,它们的使用方式略有不同. @Value("${dbpc.path}")是Spring框架中的注解之一,用于从Spri ...

  8. 能源管理系统EMS与IEC61850

    接上上文,板上运行提示缺少某些库,可能是因为交叉编译工具版本太高了.后续使用vitis自带的交叉编译工具编译,然后放入Xilinx开发板运行成功. EMS,即能源管理系统,是储能"3s&qu ...

  9. C语言中include的冷知识——聊聊大家熟悉又陌生的include

    摘自:https://mp.weixin.qq.com/s/4e8_0SK4mrInJq1dEy4lFQ 不管你是初学者还是高手,在学习了解C语言的时候,都学过include这个知识. 而我们最熟悉最 ...

  10. python语法 函数篇

    1.函数定义 #def 定义一个函数 def caculator_BMI(weight,height): BMI = float((weight / (height ** 2))) print(&qu ...