PHP面向对象编程

全面学习PHP面向对象编程的核心概念和实践

面向对象编程概述

面向对象编程(OOP)是一种编程范式,它使用"对象"来设计应用程序和软件。PHP从版本5开始全面支持面向对象编程。

OOP的主要概念

  • 类(Class): 对象的蓝图或模板
  • 对象(Object): 类的实例
  • 属性(Properties): 对象的特征或数据
  • 方法(Methods): 对象的行为或功能
  • 继承(Inheritance): 子类继承父类的特性
  • 封装(Encapsulation): 隐藏对象的内部实现细节
  • 多态(Polymorphism): 不同对象对同一消息做出不同响应
  • 抽象(Abstraction): 简化复杂现实,只关注相关特征
OOP的优势:
  • 代码重用: 通过继承实现代码复用
  • 模块化: 代码组织更清晰,易于维护
  • 扩展性: 易于添加新功能
  • 安全性: 通过封装保护数据
  • 团队协作: 不同开发者可以负责不同类

类和对象

类是对象的模板,对象是类的实例。

定义类和创建对象
<?php
// 定义一个类
class Car {
    // 属性
    public $brand;
    public $color;
    public $price;
    
    // 构造方法
    public function __construct($brand, $color, $price) {
        $this->brand = $brand;
        $this->color = $color;
        $this->price = $price;
    }
    
    // 方法
    public function getInfo() {
        return "这是一辆" . $this->color . "的" . $this->brand . ",价格:" . $this->price;
    }
    
    public function startEngine() {
        return "引擎已启动!";
    }
}

// 创建对象
$car1 = new Car("丰田", "红色", "150000");
$car2 = new Car("本田", "蓝色", "180000");

// 调用方法
echo $car1->getInfo(); // 输出:这是一辆红色的丰田,价格:150000
echo "<br>";
echo $car2->startEngine(); // 输出:引擎已启动!
?>

构造方法和析构方法

构造方法和析构方法示例
<?php
class Person {
    public $name;
    public $age;
    
    // 构造方法
    public function __construct($name, $age) {
        $this->name = $name;
        $this->age = $age;
        echo "创建Person对象: $name<br>";
    }
    
    // 析构方法
    public function __destruct() {
        echo "销毁Person对象: $this->name<br>";
    }
    
    public function introduce() {
        return "我是$this->name,今年$this->age岁。";
    }
}

$person1 = new Person("张三", 25);
echo $person1->introduce() . "<br>";

$person2 = new Person("李四", 30);
echo $person2->introduce() . "<br>";

// 对象销毁时会自动调用析构方法
unset($person1);
unset($person2);
?>

访问控制修饰符

PHP提供了三种访问控制修饰符:

修饰符 描述 访问范围
public 公有的 在任何地方都可以访问
protected 受保护的 在类内部和子类中可以访问
private 私有的 只能在类内部访问
访问控制示例
<?php
class AccessExample {
    public $publicVar = "公有属性";
    protected $protectedVar = "受保护属性";
    private $privateVar = "私有属性";
    
    public function getPrivateVar() {
        return $this->privateVar; // 在类内部可以访问私有属性
    }
    
    protected function protectedMethod() {
        return "受保护的方法";
    }
    
    private function privateMethod() {
        return "私有的方法";
    }
}

$obj = new AccessExample();
echo $obj->publicVar; // 可以访问
// echo $obj->protectedVar; // 错误:不能访问受保护属性
// echo $obj->privateVar; // 错误:不能访问私有属性
echo $obj->getPrivateVar(); // 通过公有方法访问私有属性
?>

继承

继承允许一个类(子类)继承另一个类(父类)的属性和方法。

继承示例
<?php
// 父类
class Animal {
    protected $name;
    protected $sound;
    
    public function __construct($name, $sound) {
        $this->name = $name;
        $this->sound = $sound;
    }
    
    public function speak() {
        return $this->name . "说:" . $this->sound;
    }
    
    public function eat() {
        return $this->name . "正在吃东西";
    }
}

// 子类
class Dog extends Animal {
    private $breed;
    
    public function __construct($name, $breed) {
        parent::__construct($name, "汪汪");
        $this->breed = $breed;
    }
    
    // 重写父类方法
    public function speak() {
        return $this->name . "(" . $this->breed . ")大声叫:" . $this->sound . "!";
    }
    
    // 子类特有的方法
    public function fetch() {
        return $this->name . "正在捡球";
    }
}

// 使用子类
$dog = new Dog("旺财", "金毛");
echo $dog->speak(); // 输出:旺财(金毛)大声叫:汪汪!
echo "<br>";
echo $dog->eat(); // 继承自父类
echo "<br>";
echo $dog->fetch(); // 子类特有的方法
?>

final关键字

使用final关键字可以防止类被继承或方法被重写。

final关键字示例
<?php
final class FinalClass {
    // 这个类不能被继承
}

class ParentClass {
    final public function finalMethod() {
        return "这个方法不能被子类重写";
    }
}

class ChildClass extends ParentClass {
    // 尝试重写final方法会导致错误
    // public function finalMethod() { }
}
?>

静态属性和方法

静态属性和方法属于类本身,而不是类的实例。

静态成员示例
<?php
class Counter {
    public static $count = 0;
    
    public function __construct() {
        self::$count++;
    }
    
    public static function getCount() {
        return self::$count;
    }
    
    public static function resetCount() {
        self::$count = 0;
    }
}

// 使用静态属性和方法
$obj1 = new Counter();
$obj2 = new Counter();
$obj3 = new Counter();

echo "创建的对象数量:" . Counter::getCount(); // 输出:3
echo "<br>";
echo "计数器值:" . Counter::$count; // 输出:3

Counter::resetCount();
echo "<br>重置后计数器值:" . Counter::getCount(); // 输出:0
?>

接口和抽象类

接口

接口定义了一个类必须实现的方法,但不提供具体实现。

接口示例
<?php
// 定义接口
interface Vehicle {
    public function start();
    public function stop();
    public function getInfo();
}

// 实现接口
class Car implements Vehicle {
    public function start() {
        return "汽车启动";
    }
    
    public function stop() {
        return "汽车停止";
    }
    
    public function getInfo() {
        return "这是一辆汽车";
    }
}

class Bicycle implements Vehicle {
    public function start() {
        return "自行车开始骑行";
    }
    
    public function stop() {
        return "自行车停止";
    }
    
    public function getInfo() {
        return "这是一辆自行车";
    }
}

$car = new Car();
echo $car->start();
echo "<br>";

$bicycle = new Bicycle();
echo $bicycle->start();
?>

抽象类

抽象类不能实例化,只能被继承,可以包含抽象方法和具体方法。

抽象类示例
<?php
// 定义抽象类
abstract class Shape {
    protected $name;
    
    public function __construct($name) {
        $this->name = $name;
    }
    
    // 抽象方法,必须在子类中实现
    abstract public function area();
    abstract public function perimeter();
    
    // 具体方法
    public function getName() {
        return $this->name;
    }
}

// 实现抽象类
class Rectangle extends Shape {
    private $width;
    private $height;
    
    public function __construct($width, $height) {
        parent::__construct("矩形");
        $this->width = $width;
        $this->height = $height;
    }
    
    public function area() {
        return $this->width * $this->height;
    }
    
    public function perimeter() {
        return 2 * ($this->width + $this->height);
    }
}

class Circle extends Shape {
    private $radius;
    
    public function __construct($radius) {
        parent::__construct("圆形");
        $this->radius = $radius;
    }
    
    public function area() {
        return pi() * pow($this->radius, 2);
    }
    
    public function perimeter() {
        return 2 * pi() * $this->radius;
    }
}

$rect = new Rectangle(5, 3);
echo $rect->getName() . "面积:" . $rect->area();
echo "<br>";
echo $rect->getName() . "周长:" . $rect->perimeter();
echo "<br><br>";

$circle = new Circle(4);
echo $circle->getName() . "面积:" . round($circle->area(), 2);
echo "<br>";
echo $circle->getName() . "周长:" . round($circle->perimeter(), 2);
?>

魔术方法

PHP提供了一系列以双下划线开头的魔术方法,它们在特定情况下自动调用。

常用魔术方法
<?php
class MagicMethods {
    private $data = [];
    
    // __get 在访问不可访问属性时调用
    public function __get($name) {
        return isset($this->data[$name]) ? $this->data[$name] : null;
    }
    
    // __set 在给不可访问属性赋值时调用
    public function __set($name, $value) {
        $this->data[$name] = $value;
    }
    
    // __toString 在对象被当作字符串时调用
    public function __toString() {
        return "这是一个MagicMethods对象";
    }
    
    // __call 在调用不可访问方法时调用
    public function __call($name, $arguments) {
        return "调用了不存在的方法:$name,参数:" . implode(", ", $arguments);
    }
    
    // __callStatic 在静态调用不可访问方法时调用
    public static function __callStatic($name, $arguments) {
        return "静态调用了不存在的方法:$name";
    }
    
    // __isset 在对不可访问属性调用isset()或empty()时调用
    public function __isset($name) {
        return isset($this->data[$name]);
    }
    
    // __unset 在对不可访问属性调用unset()时调用
    public function __unset($name) {
        unset($this->data[$name]);
    }
}

$obj = new MagicMethods();
$obj->name = "测试"; // 调用 __set
echo $obj->name; // 调用 __get
echo "<br>";
echo $obj; // 调用 __toString
echo "<br>";
echo $obj->undefinedMethod("参数1", "参数2"); // 调用 __call
echo "<br>";
echo MagicMethods::undefinedStaticMethod(); // 调用 __callStatic
echo "<br>";
echo "name属性存在吗? " . (isset($obj->name) ? "是" : "否"); // 调用 __isset
?>

命名空间

命名空间用于解决命名冲突,组织和管理代码。

命名空间示例
<?php
// 文件: utils/Validator.php
namespace Utils;

class Validator {
    public static function validateEmail($email) {
        return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
    }
}

// 文件: services/UserService.php
namespace Services;

class UserService {
    public function createUser($email) {
        // 使用完全限定名称
        if (\Utils\Validator::validateEmail($email)) {
            return "用户创建成功";
        }
        return "邮箱格式无效";
    }
}

// 使用命名空间
$userService = new \Services\UserService();
echo $userService->createUser("test@example.com");
?>

Trait特性

Trait是PHP 5.4引入的代码复用机制,可以在不同类中复用方法。

Trait示例
<?php
trait Logger {
    public function log($message) {
        $timestamp = date("Y-m-d H:i:s");
        echo "[$timestamp] $message<br>";
    }
}

trait Timestamp {
    public function getTimestamp() {
        return time();
    }
}

class User {
    use Logger, Timestamp;
    
    public function register() {
        $this->log("用户注册成功");
        return "注册时间: " . $this->getTimestamp();
    }
}

class Product {
    use Logger;
    
    public function create() {
        $this->log("产品创建成功");
        return "产品已创建";
    }
}

$user = new User();
echo $user->register();
echo "<br>";

$product = new Product();
echo $product->create();
?>

面向对象设计原则

编写高质量面向对象代码的一些指导原则:

SOLID原则

  • 单一职责原则 (SRP): 一个类应该只有一个引起变化的原因
  • 开放封闭原则 (OCP): 对扩展开放,对修改封闭
  • 里氏替换原则 (LSP): 子类应该能够替换父类
  • 接口隔离原则 (ISP): 使用多个专门的接口比使用单一的总接口好
  • 依赖倒置原则 (DIP): 依赖于抽象而不是具体实现
依赖注入示例
<?php
interface LoggerInterface {
    public function log($message);
}

class FileLogger implements LoggerInterface {
    public function log($message) {
        // 记录到文件
        echo "记录到文件: $message<br>";
    }
}

class DatabaseLogger implements LoggerInterface {
    public function log($message) {
        // 记录到数据库
        echo "记录到数据库: $message<br>";
    }
}

class UserService {
    private $logger;
    
    // 依赖注入
    public function __construct(LoggerInterface $logger) {
        $this->logger = $logger;
    }
    
    public function register($username) {
        // 注册逻辑...
        $this->logger->log("用户 $username 注册成功");
        return "用户注册成功";
    }
}

// 使用不同的日志实现
$fileLogger = new FileLogger();
$userService = new UserService($fileLogger);
echo $userService->register("张三");

$dbLogger = new DatabaseLogger();
$userService2 = new UserService($dbLogger);
echo $userService2->register("李四");
?>

实践练习

创建一个完整的面向对象PHP应用程序,实现以下功能:

  1. 设计一个简单的电子商务系统
  2. 创建Product类、User类、Order类等
  3. 使用继承创建不同的产品类别
  4. 使用接口定义通用行为
  5. 使用命名空间组织代码
  6. 实现依赖注入
  7. 使用Trait复用代码
  8. 应用SOLID原则
提示: 可以从简单的类开始,逐步添加功能,确保每个类都有明确的职责。