PHP 之高级面向对象编程 深入理解设计模式、原则与性能优化

PHP 之高级面向对象编程 深入理解设计模式、原则与性能优化

面向对象编程(Object-Oriented Programming, OOP)不只是"如何写 class"的语法规则。它更像一种组织软件系统的思维方式:通过清晰的边界、职责拆分与对象协作,让系统更容易理解、扩展和维护。

当你已经掌握了类、对象、属性、方法这些基础概念之后,就可以把视角往更深一层挪一挪:设计模式、SOLID 原则,以及在大型系统里绕不开的性能与内存问题。

本文会围绕几个常见主题展开:策略模式与单例模式、SOLID 五原则,以及不可变对象与内存管理相关的性能考虑。它们的目的不是"炫技",而是让真实项目在后续迭代中更稳、更好改。

原文链接 PHP 之高级面向对象编程 深入理解设计模式、原则与性能优化

面向对象编程中的设计模式

设计模式是对软件设计中常见问题的可复用解决方案。它们不是代码模板,而是可以在不同场景下调整和落地的一组设计思路。

策略模式:将算法与类解耦

策略模式(Strategy Pattern)可以把一组可互换的算法抽出来,用统一的接口对外暴露,让使用方在运行时自由选择实现,而不需要把具体算法硬编码进业务类。

当你希望"根据条件切换算法",又不希望让一个类膨胀到塞满 if/else 时,策略模式通常很好用。

以电商折扣为例:可能有"打折""买一送一"等不同折扣策略。如果把所有折扣规则都堆在 Cart 里,Cart 会越来越难维护。用策略模式则可以把折扣计算拆到不同策略类里。

php 复制代码
<?php

interface DiscountStrategy {
    public function apply(float $total): float;
}

class PercentDiscount implements DiscountStrategy {
    public function __construct(private float $percent) {}

    public function apply(float $total): float {
        return $total * (1 - $this->percent);
    }
}

class BuyOneGetOneFree implements DiscountStrategy {
    public function apply(float $total): float {
        // 简单模拟:买一送一即总价减半
        return $total / 2;
    }
}

class Cart {
    private array $items = [];

    // 依赖注入:注入策略接口而非具体实现
    public function __construct(private DiscountStrategy $strategy) {}

    public function addItem(float $price): void {
        $this->items[] = $price;
    }

    public function total(): float {
        $subtotal = array_sum($this->items);
        return $this->strategy->apply($subtotal);
    }
}

// 使用示例
$cart = new Cart(new PercentDiscount(0.1)); // 打九折
$cart->addItem(100);
$cart->addItem(200);
echo $cart->total(); // 输出: 270

这里 Cart 不需要知道"折扣怎么算",它只依赖一个抽象的 DiscountStrategy。想更换折扣规则,只要传入不同的策略实现即可,无需修改 Cart

单例模式:确保只有一个实例

单例模式(Singleton Pattern)用于保证某个类在应用生命周期内只有一个实例,并提供一个全局访问点。

它常用于管理共享资源,例如数据库连接、配置对象等:这类对象通常不希望被频繁创建,也不希望出现多个实例导致状态不一致。

php 复制代码
<?php

class DatabaseConnection {
    private static ?DatabaseConnection $instance = null;

    // 私有化构造函数,防止外部 new
    private function __construct() {
        echo "连接到数据库...\n";
    }

    // 防止克隆
    private function __clone() {}

    // 防止反序列化
    public function __wakeup() {
        throw new \Exception("Cannot unserialize singleton");
    }

    public static function getInstance(): DatabaseConnection {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}

// 使用示例
$db1 = DatabaseConnection::getInstance();
$db2 = DatabaseConnection::getInstance();

var_dump($db1 === $db2); // 输出: bool(true)

上面 DatabaseConnection 通过重写 __new__ 来确保无论实例化多少次,返回的都是同一个对象。

SOLID 原则

SOLID 是一组面向对象设计原则,目标是让系统更易维护、可扩展、可测试。把这些原则落到日常编码里,能减少很多常见的"面向对象写着写着就变成一团"的问题。

单一职责原则(SRP)

单一职责原则(Single Responsibility Principle)强调:一个类应该只有一个引起它变化的原因。换句话说,一个类最好只负责一件事。

例如 User 既保存用户数据又负责发送通知,会同时承担"数据模型"和"通知逻辑"两种职责。可以按 SRP 拆分:

php 复制代码
<?php

class User {
    public function __construct(public string $name) {}
}

class UserNotification {
    public function sendWelcomeEmail(User $user): void {
        echo "正在向 {$user->name} 发送欢迎邮件。\n";
    }
}

// 使用
$user = new User("张三");
$notifier = new UserNotification();
$notifier->sendWelcomeEmail($user);

拆分之后,每个类的职责更集中,测试与修改也更可控。

开放/封闭原则(OCP)

开放/封闭原则(Open/Closed Principle)要求:对扩展开放,对修改封闭。也就是在不改动既有代码的前提下,能够通过扩展来引入新行为。

下面用形状面积示例说明:Shape 定义接口,不同形状通过继承实现 area(),调用方不需要知道具体类型。

php 复制代码
<?php

interface Shape {
    public function area(): float;
}

class Circle implements Shape {
    public function __construct(private float $radius) {}
    public function area(): float {
        return pi() * pow($this->radius, 2);
    }
}

class Rectangle implements Shape {
    public function __construct(private float $width, private float $height) {}
    public function area(): float {
        return $this->width * $this->height;
    }
}

function printArea(Shape $shape) {
    echo "面积: " . $shape->area() . "\n";
}

// 使用
printArea(new Circle(5));
printArea(new Rectangle(4, 6));

当你要新增一种形状时,只需要添加新子类即可,不必改动 print_area 或既有形状代码。

里氏替换原则(LSP)

里氏替换原则(Liskov Substitution Principle)指出:基类能出现的地方,子类也应该能替换进去,并且不影响程序正确性。它要求子类是对父类能力的"真实扩展",而不是"表面继承"。

下面这个例子里,Penguin 继承 Bird 但无法飞行,导致替换失败:

php 复制代码
<?php

class Bird {
    public function fly(): void {
        echo "正在飞行...\n";
    }
}

class Penguin extends Bird {
    public function fly(): void {
        // 企鹅不会飞,违反了父类 Bird 的行为预期
        throw new Exception("企鹅不会飞!");
    }
}

/** * 修正方案:拆分接口
 * interface Bird {}
 * interface FlyingBird extends Bird { public function fly(); }
 * class Penguin implements Bird { ... }
 */

这个设计违反了 LSP:Penguin 无法在需要 Bird.fly() 的上下文中正常工作。要解决它,通常需要重新审视抽象(例如把"会飞"能力从 Bird 中拆出来,或者调整继承层次)。

接口隔离原则(ISP)

接口隔离原则(Interface Segregation Principle)强调:客户端不应该被迫依赖自己用不到的方法。与其做一个"很大很全"的接口,不如拆成多个小而聚焦的接口,让使用方按需组合。

php 复制代码
<?php

interface Workable {
    public function work(): void;
}

interface Eatable {
    public function eat(): void;
}

class HumanWorker implements Workable, Eatable {
    public function work(): void { echo "人类在工作\n"; }
    public function eat(): void { echo "人类在吃饭\n"; }
}

class RobotWorker implements Workable {
    public function work(): void { echo "机器人在工作\n"; }
    // 机器人不需要实现 eat() 接口
}

这里 WorkerEater 拆成两个接口,避免了让所有实现者都背上不需要的职责。

依赖倒置原则(DIP)

依赖倒置原则(Dependency Inversion Principle)主张:高层模块不应该依赖低层模块,二者都应该依赖抽象(接口/抽象类)。这样可以降低耦合,让替换实现更容易。

php 复制代码
<?php

interface Switchable {
    public function turnOn(): void;
    public function turnOff(): void;
}

class LightBulb implements Switchable {
    public function turnOn(): void { echo "灯亮了\n"; }
    public function turnOff(): void { echo "灯灭了\n"; }
}

class Switcher {
    // 依赖于接口,而不是具体的 LightBulb 类
    public function __construct(private Switchable $device) {}

    public function operate(): void {
        $this->device->turnOn();
    }
}

$bulb = new LightBulb();
$switch = new Switcher($bulb);
$switch->operate();

这里 Switch 直接依赖 LightBulb。如果把依赖改为抽象接口(比如 Switchable),Switch 就能支持更多设备实现,而不需要改动自身代码。

面向对象编程中的性能优化与内存考量

OOP 通常更强调清晰与可维护,但在大型系统里,性能与内存开销同样需要被考虑。理解对象创建、内存分配以及不可变性带来的影响,有助于在"可读性"和"效率"之间取得更好的平衡。

为值对象引入不可变性

PHP 8.2 引入了 readonly class,这非常适合实现不可变的值对象。不可变对象在处理值类型(Value Object)时很常见,例如 MoneyDateRangeCoordinate。把对象设计成不可变,可以减少共享状态带来的副作用,也更利于推理与并发场景下的安全性。

php 复制代码
<?php

readonly class Money {
    public function __construct(
        public string $currency,
        public int $amount
    ) {}

    public function add(Money $other): Money {
        if ($this->currency !== $other->currency) {
            throw new InvalidArgumentException("币种不匹配");
        }
        // 返回新实例,而不是修改当前对象
        return new Money($this->currency, $this->amount + $other->amount);
    }
}

$m1 = new Money("CNY", 100);
$m2 = new Money("CNY", 50);
$result = $m1->add($m2);

echo $result->amount; // 150
// $m1->amount = 200; // 会报错,因为是 readonly

在这个例子里,Money 是不可变的:每次运算都会返回一个新实例,原对象保持不变。

避免对象抖动(Object Churn)

在紧密循环里频繁创建和销毁对象,会带来明显的性能问题:更多的内存分配、更高的垃圾回收(GC)压力。

遇到这种情况,可以考虑对象池(object pool)或复用对象,减少不必要的创建与回收开销。

总结:把原则与模式落到代码里

面向对象编程提供了一套组织复杂系统的工具。设计模式(例如 Strategy、Singleton)帮助你复用成熟的结构;SOLID 原则提醒你如何划分职责、控制依赖;而不可变对象、对象创建成本等性能视角则让系统在规模化场景下更稳。

OOP 的重点不在"有多少类",而在抽象是否清晰、边界是否明确、实现是否可替换、变化是否被限制在可控的范围内。把这些要点坚持下来,代码通常会更好读,也更好维护。

相关推荐
Lisonseekpan1 小时前
为什么Spring 推荐使用构造器注入而非@Autowired字段注入?
java·后端·spring·log4j
草莓熊Lotso1 小时前
Python 流程控制完全指南:条件语句 + 循环语句 + 实战案例(零基础入门)
android·开发语言·人工智能·经验分享·笔记·后端·python
laozhoy11 小时前
深入理解Golang中的锁机制
开发语言·后端·golang
码luffyliu1 小时前
Go 中的深浅拷贝:从城市缓存场景讲透指针与内存操作
后端·go·指针·浅拷贝·深拷贝
老华带你飞2 小时前
个人网盘管理|基于springboot + vue个人网盘管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端
百***78752 小时前
LLaMA 4 API国内稳定接入指南:中转服务全链路实操与优化方案
开发语言·php·llama
JaguarJack2 小时前
PHP 之高级面向对象编程 深入理解设计模式、原则与性能优化
后端·php
Laravel技术社区3 小时前
用PHP8实现斗地主游戏,实现三带一,三带二,四带二,顺子,王炸功能(第二集)
前端·游戏·php
郑州光合科技余经理3 小时前
PHP构建:支撑欧美澳市场的同城生活服务平台开发
java·开发语言·数据库·uni-app·php·排序算法·生活