PHP 类和对象怎么理解?从简单实例进入面向对象编程
如果刚开始学习 PHP 面向对象编程,很多人都会被“类、对象、属性、方法、实例化、封装、继承、多态”这一串术语绕进去。看起来每个词都不难,放在一起却很容易变成一锅概念粥。
其实,理解 PHP 的类和对象并不需要从复杂的设计模式开始。先搞清楚一个问题就够了:当程序不再只是处理几行数据,而是需要长期管理某种状态,并围绕这些状态提供一组操作时,怎样让代码保持清晰?
类和对象就是解决这类问题的一种组织方式。
例如,一个汽车管理程序可能需要保存汽车品牌、型号和颜色,同时还需要提供启动、停车、修改状态等操作。如果所有数据都散落在数组和变量中,代码规模一大,就容易出现“这个变量属于哪辆车”“这个函数修改的是哪份数据”之类的问题。
PHP 可以把相关的数据和操作组织到一个类中:
class Car
{
public string $brand = 'Toyota';
public string $model = 'Corolla';
public string $color = 'Blue';
public function startEngine(): void
{
echo "Engine started.";
}
}
这里的 Car 是一个类。它描述了这一类对象具有哪些属性,以及可以执行哪些操作。
当程序执行:
$myCar = new Car();
new 会创建一个 Car 类的对象,并把这个对象的引用赋给 $myCar。此后可以通过对象访问公开属性和方法:
echo $myCar->brand;
$myCar->startEngine();
初学者可以暂时把类理解成一种结构定义,把对象理解成根据这个结构创建出来的具体实例。不过,这个比喻只能帮助入门。类并不是一张简单的“现实世界模板”,对象也不是把所有方法复制到某个变量里面。PHP 对象是运行时的数据结构,方法属于类的方法定义,对象保存自己的实例状态。
这个区别在程序变复杂以后非常重要。
例如:
$car1 = new Car();
$car2 = new Car();
$car1->color = 'Red';
$car2->color = 'Black';
这里有两个不同的对象。它们来自同一个 Car 类,因此具有相同的属性和方法定义,但每个对象可以拥有自己的属性值。
这就是对象的重要意义之一:代码可以同时管理多个相互独立的状态,而不需要为每一份数据重新写一套函数。
另一个核心概念是访问控制。
PHP 提供了 public、protected 和 private 等访问修饰符。public 表示外部代码可以访问,private 表示只能在声明它的类内部访问,protected 则允许声明它的类以及继承体系中的相关类访问。
比如银行账户的余额通常不应该允许外部代码随便修改:
class BankAccount
{
private float $balance = 0;
public function deposit(float $amount): void
{
if ($amount <= 0) {
throw new InvalidArgumentException('Amount must be positive.');
}
$this->balance += $amount;
}
public function getBalance(): float
{
return $this->balance;
}
}
外部代码不能直接执行:
$account->balance = -100000;
因为 $balance 是 private。
如果需要增加余额,就应该通过 deposit() 这样的公开方法完成。这样做的意义并不只是“保护变量”,更重要的是把修改状态的规则集中起来。
这就是封装。
封装并不意味着所有属性都必须设置成 private,然后机械地为每个属性生成一个 getter 和 setter。真正有价值的封装,是让对象负责维护自己的有效状态,并控制哪些操作可以从外部进行。
构造函数也是理解对象生命周期时非常重要的一环。
例如用户对象:
class User
{
private string $username;
private string $email;
private string $passwordHash;
public function __construct(
string $username,
string $email,
string $password
) {
$this->username = $username;
$this->email = $email;
$this->passwordHash = password_hash(
$password,
PASSWORD_DEFAULT
);
}
public function login(string $inputPassword): bool
{
return password_verify(
$inputPassword,
$this->passwordHash
);
}
}
这里的 __construct() 是 PHP 的构造方法。执行:
$user = new User(
'john',
'john@example.com',
'secret123'
);
创建对象时,构造方法会自动执行。
密码也没有直接保存为明文,而是通过 password_hash() 生成密码哈希。登录时再使用 password_verify() 检查用户输入的密码是否匹配。
这里可以看到类和对象真正有用的一面:User 不只是保存三个字段,它还可以负责与这些状态直接相关的行为。
不过,这并不意味着所有业务逻辑都应该塞进 User 类。
例如发送邮件、处理支付、调用第三方 API、生成报表等工作,如果全部塞进 User 类,最后很可能得到一个几百行甚至上千行的“万能 User”。这种设计看起来很面向对象,实际上只是把混乱从函数搬进了类。
大型 PHP 应用通常会把不同职责拆开。例如 Product 可以负责商品自身的数据和规则,Order 负责订单状态,PaymentService 负责支付流程,MailService 负责邮件发送,Repository 负责数据访问。
这时候,面向对象编程的价值就从“把变量放进 class”进一步变成了“划分职责”。
PHP 的继承也是面向对象编程的重要机制。例如:
class Animal
{
public function speak(): string
{
return 'Some sound';
}
}
class Dog extends Animal
{
public function speak(): string
{
return 'Woof';
}
}
Dog 通过 extends 继承了 Animal,同时可以重新定义 speak()。
但继承并不是代码复用的万能按钮。现代 PHP 项目中,很多情况下组合和接口比深层继承更加灵活。例如程序真正关心的可能不是“这个对象是不是某个父类的子类”,而是“这个对象是否提供某种能力”。
接口就是解决这种问题的重要工具:
interface PaymentGateway
{
public function pay(float $amount): bool;
}
之后可以让不同的支付实现遵守同一个接口:
class StripeGateway implements PaymentGateway
{
public function pay(float $amount): bool
{
// Stripe payment logic
return true;
}
}
class PaypalGateway implements PaymentGateway
{
public function pay(float $amount): bool
{
// PayPal payment logic
return true;
}
}
调用支付业务的代码只需要依赖 PaymentGateway 这个接口,而不一定需要知道具体使用哪一家支付服务。
这就是多态在实际开发中的价值:同一个抽象接口,可以对应不同的具体实现。
随着 PHP 版本发展,现代 PHP 的类型系统也让类的设计比早期 PHP 更清晰。例如属性和方法可以明确声明类型:
class Product
{
public function __construct(
private string $name,
private float $price
) {}
public function getPrice(): float
{
return $this->price;
}
}
这里使用了 PHP 8 引入的构造函数属性提升,把属性声明和构造函数参数结合起来,可以明显减少样板代码。
但语法变得漂亮,并不意味着设计自动变得正确。一个类到底应该保存什么状态、暴露哪些方法、依赖哪些对象,以及哪些业务规则应该放在哪里,依然需要开发者进行判断。
这也是很多初学者学习 OOP 时容易走偏的地方。
看到项目里到处都是 class,并不代表项目设计得好。如果只是把原来的每个函数机械地改成一个类,或者为了“面向对象”给每一个数组都包装一层对象,代码反而可能更加复杂。
一个只有两个字段、只需要简单传递的数据结构,并不一定需要设计成拥有十几个方法的复杂对象。相反,一个涉及状态变化、权限控制、生命周期和业务规则的领域对象,就很适合通过类来建立清晰的边界。
因此,学习 PHP 类和对象时,不应该把目标停留在“会写 class”这一层。
更重要的是理解三个层次。第一层是对象如何保存状态,第二层是对象如何通过方法管理这些状态,第三层则是多个对象之间如何形成清晰的职责关系。
等程序从几十行增长到几千行、几万行以后,真正决定代码是否容易维护的,往往不是用了多少个 class,而是每个类是否知道自己应该负责什么,也是否明确知道哪些事情不应该由自己负责。
面向对象编程不是为了把代码变得更加复杂,而是为了在复杂度已经出现的时候,给它建立边界。好的 OOP 设计不会让程序看起来像一座堆满术语的城堡,而应该让开发者打开代码时,很快就能看懂数据在哪里、规则在哪里、谁负责修改状态,以及不同部分为什么彼此分开。对 PHP 开发者来说,这比背下“继承、封装、多态”几个名词重要得多。
喜欢这篇报道?
使用下面的功能,方便以后继续阅读和分享 MNewsTV
关于文章收藏
收藏不需要注册帐号,收藏信息仅保存在当前浏览器中。
删除收藏请进入「我的收藏」进行管理。
分享 Facebook | X | WhatsApp | LinkedIn
捐助(Paypal): https://www.paypal.me/observeccp 订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP