PHP8 类和接口:面向对象编程的核心

PHP8 类和接口:面向对象编程的核心

PHP8 对面向对象编程(OOP)进行了多项增强,引入了新特性并改进了现有功能,使类和接口的使用更加灵活和强大。本文将详细介绍 PHP8 中类和接口的定义、特性及新功能。

类的基本概念与定义

类是面向对象编程的基本构建块,用于封装数据和操作数据的方法。

1. 基本类定义

<?php
class Person {
    // 属性
    public $name;
    public $age;
    
    // 构造方法
    public function __construct(string $name, int $age) {
        $this->name = $name;
        $this->age = $age;
    }
    
    // 方法
    public function greet() {
        return "Hello, my name is {$this->name} and I'm {$this->age} years old.";
    }
    
    public function birthday() {
        $this->age++;
    }
}

// 创建对象
$person = new Person("John", 30);
echo $person->greet(); // 输出:Hello, my name is John and I'm 30 years old.

$person->birthday();
echo $person->greet(); // 输出:Hello, my name is John and I'm 31 years old.
?>

2. 属性与访问修饰符

PHP 支持三种访问修饰符,用于控制类成员的访问权限:

  • public:公共成员,可以在任何地方访问
  • protected:受保护成员,只能在类内部和子类中访问
  • private:私有成员,只能在类内部访问
<?php
class BankAccount {
    public $accountNumber;      // 公共属性
    protected $balance;         // 受保护属性
    private $pinCode;           // 私有属性
    
    public function __construct(string $accountNumber, float $balance, string $pinCode) {
        $this->accountNumber = $accountNumber;
        $this->balance = $balance;
        $this->pinCode = $pinCode;
    }
    
    // 公共方法可以访问所有属性
    public function getBalance(string $pin) {
        if ($this->verifyPin($pin)) {
            return $this->balance;
        }
        return "Invalid PIN";
    }
    
    // 受保护方法
    protected function addFunds(float $amount) {
        $this->balance += $amount;
    }
    
    // 私有方法
    private function verifyPin(string $pin) {
        return $pin === $this->pinCode;
    }
}

$account = new BankAccount("123456", 1000.00, "1234");
echo $account->accountNumber; // 可以访问公共属性
echo $account->getBalance("1234"); // 可以通过公共方法访问受保护和私有属性
// echo $account->balance; // 错误:不能直接访问受保护属性
// echo $account->pinCode; // 错误:不能直接访问私有属性
?>

PHP8 类的新特性

1. 构造方法属性提升(PHP8.0+)

PHP8 引入了构造方法属性提升,允许在构造方法参数中直接声明类属性,简化代码。

<?php
// 传统方式
class UserOld {
    public $name;
    public $email;
    
    public function __construct(string $name, string $email) {
        $this->name = $name;
        $this->email = $email;
    }
}

// PHP8 构造方法属性提升
class UserNew {
    // 在构造方法参数中直接声明属性
    public function __construct(
        public string $name,
        public string $email
    ) {
        // 无需赋值,PHP自动处理
    }
}

$user = new UserNew("John Doe", "john@example.com");
echo $user->name; // 输出:John Doe
echo $user->email; // 输出:john@example.com
?>

2. 只读属性(PHP8.1+)

PHP8.1 引入了 readonly 关键字,用于声明只能初始化一次且不能修改的属性。

<?php
class Product {
    public readonly string $name;
    public readonly float $price;
    
    public function __construct(string $name, float $price) {
        $this->name = $name;
        $this->price = $price;
    }
}

$product = new Product("Laptop", 999.99);
echo $product->name; // 输出:Laptop

// $product->name = "Phone"; // 错误:Cannot modify readonly property
?>

3. 联合类型(PHP8.0+)

PHP8 支持联合类型,表示一个参数或返回值可以是多种类型中的一种。

<?php
class Calculator {
    // 接受int或float类型的参数
    public function add(int|float $a, int|float $b): int|float {
        return $a + $b;
    }
}

$calc = new Calculator();
echo $calc->add(2, 3); // 输出:5(int)
echo $calc->add(2.5, 3.5); // 输出:6(float)
echo $calc->add(2, 3.5); // 输出:5.5(float)
?>

4. 命名参数(PHP8.0+)

PHP8 支持命名参数,允许在调用方法时通过参数名指定值,而不必严格按照参数顺序。

<?php
class Message {
    public function send(string $to, string $subject, string $body): void {
        echo "Sending to: $to\n";
        echo "Subject: $subject\n";
        echo "Body: $body\n";
    }
}

$message = new Message();

// 使用命名参数,无需考虑顺序
$message->send(
    subject: "Hello",
    to: "john@example.com",
    body: "This is a test message"
);
?>

继承与多态

1. 类的继承

PHP 支持单继承,使用 extends 关键字实现类的继承。

<?php
class Animal {
    protected string $name;
    
    public function __construct(string $name) {
        $this->name = $name;
    }
    
    public function getName(): string {
        return $this->name;
    }
    
    public function makeSound(): string {
        return "Some generic sound";
    }
}

// 继承Animal类
class Dog extends Animal {
    // 重写父类方法
    public function makeSound(): string {
        return "Woof! Woof!";
    }
    
    // 子类新增方法
    public function fetch(): string {
        return "{$this->name} is fetching the ball";
    }
}

$dog = new Dog("Buddy");
echo $dog->getName(); // 输出:Buddy(继承的方法)
echo $dog->makeSound(); // 输出:Woof! Woof!(重写的方法)
echo $dog->fetch(); // 输出:Buddy is fetching the ball(新增的方法)
?>

2. 抽象类与抽象方法

抽象类使用 abstract 关键字声明,不能被实例化,只能被继承。抽象方法没有实现,必须在子类中实现。

<?php
abstract class Shape {
    protected string $color;
    
    public function __construct(string $color) {
        $this->color = $color;
    }
    
    public function getColor(): string {
        return $this->color;
    }
    
    // 抽象方法,子类必须实现
    abstract public function getArea(): float;
}

class Circle extends Shape {
    private float $radius;
    
    public function __construct(string $color, float $radius) {
        parent::__construct($color);
        $this->radius = $radius;
    }
    
    // 实现抽象方法
    public function getArea(): float {
        return pi() * $this->radius * $this->radius;
    }
}

$circle = new Circle("red", 5);
echo $circle->getColor(); // 输出:red
echo $circle->getArea(); // 输出:78.5398...
?>

接口

接口定义了类应该实现的方法,但不提供方法的实现。类使用 implements 关键字实现接口。

1. 基本接口定义与实现

<?php
// 定义接口
interface Logger {
    public function log(string $message): void;
    public function error(string $message): void;
}

// 实现接口
class FileLogger implements Logger {
    private string $filename;
    
    public function __construct(string $filename) {
        $this->filename = $filename;
    }
    
    public function log(string $message): void {
        $this->writeToFile("LOG: " . $message);
    }
    
    public function error(string $message): void {
        $this->writeToFile("ERROR: " . $message);
    }
    
    private function writeToFile(string $message): void {
        $timestamp = date('Y-m-d H:i:s');
        file_put_contents(
            $this->filename,
            "[$timestamp] $message\n",
            FILE_APPEND
        );
    }
}

// 使用实现了接口的类
$logger = new FileLogger('app.log');
$logger->log('Application started');
$logger->error('Something went wrong');
?>

2. 接口继承

接口可以继承其他接口,使用 extends 关键字。

<?php
interface Readable {
    public function read(): string;
}

interface Writable {
    public function write(string $data): void;
}

// 继承多个接口
interface ReadWriteable extends Readable, Writable {
    public function flush(): void;
}

// 实现组合接口
class FileHandler implements ReadWriteable {
    private string $filename;
    
    public function __construct(string $filename) {
        $this->filename = $filename;
    }
    
    public function read(): string {
        return file_get_contents($this->filename);
    }
    
    public function write(string $data): void {
        file_put_contents($this->filename, $data);
    }
    
    public function flush(): void {
        // 实现刷新逻辑
    }
}
?>

3. PHP8 接口新特性

(1)接口中的常量可见性(PHP8.1+)

PHP8.1 允许为接口常量指定可见性修饰符(只能是 public)。

<?php
interface Database {
    // PHP8.1+ 允许显式指定public可见性
    public const VERSION = '1.0';
    const HOST = 'localhost'; // 默认为public
}
?>
(2)静态返回类型(PHP8.0+)

PHP8 允许在接口中使用 selfparent 作为静态返回类型。

<?php
interface Builder {
    public static function create(): self;
    public function setName(string $name): self;
    public function build();
}

class ProductBuilder implements Builder {
    private string $name = '';
    
    // 实现接口方法,返回自身类型
    public static function create(): self {
        return new self();
    }
    
    public function setName(string $name): self {
        $this->name = $name;
        return $this; // 方法链模式
    }
    
    public function build() {
        return [
            'name' => $this->name
        ];
    }
}

// 使用构建器
$product = ProductBuilder::create()
    ->setName("Laptop")
    ->build();

print_r($product);
?>

self 是 PHP 中用于指代当前类本身的关键字,主要在类的内部使用,用于访问类的静态成员(静态属性、静态方法)和类常量,也可用于调用当前类的构造方法

主要用法:
  1. 访问类常量
  2. 访问静态属性和静态方法
<?php
class MyClass {
    // 类常量
    const VERSION = '1.0';
    
    // 静态属性
    public static $count = 0;
    
    // 构造方法
    public function __construct() {
        self::$count++; // 用self访问静态属性
    }
    
    // 静态方法
    public static function getVersion() {
        return self::VERSION; // 用self访问类常量
    }
    
    // 普通方法中使用self
    public function getCount() {
        return self::$count; // 用self访问静态属性
    }
}

// 调用静态方法(通过类名::方法名)
echo MyClass::getVersion(); // 输出:1.0

// 创建对象(触发构造方法,count递增)
$obj1 = new MyClass();
$obj2 = new MyClass();

// 访问静态属性
echo MyClass::$count; // 输出:2

// 通过对象调用普通方法
echo $obj1->getCount(); // 输出:2
?>

-> 操作符: 用法是 $对象->成员 可以访问 实例属性、实例方法(非静态)

:: 操作符: 用法是 类名::成员 静态属性、静态方法、类常量

特质(Traits)

特质是一种代码复用机制,介于类和接口之间,用于解决 PHP 单继承的限制。

<?php
// 定义特质
trait Loggable {
    public function log(string $message): void {
        $class = static::class;
        echo "[$class] $message\n";
    }
}

trait Timestampable {
    private $createdAt;
    
    public function getCreatedAt() {
        return $this->createdAt;
    }
    
    public function setCreatedAt($timestamp) {
        $this->createdAt = $timestamp;
    }
}

// 使用特质
class Article {
    use Loggable, Timestampable; // 可以使用多个特质
    
    private string $title;
    
    public function __construct(string $title) {
        $this->title = $title;
        $this->setCreatedAt(time());
        $this->log("Article created: $title");
    }
}

$article = new Article("PHP8 Features");
echo "Created at: " . $article->getCreatedAt();
?>

如果使用的多个特质中有相同的方法,就会报错

// 定义特质
trait Loggable {
    public function log(string $message): void {
        $class = static::class;
        echo "[$class] $message\n";
    }
}

trait Timestampable {
    private $createdAt;
    
    public function getCreatedAt() {
        return $this->createdAt;
    }
    
    public function setCreatedAt($timestamp) {
        $this->createdAt = $timestamp;
    }

    public function log(string $message): void {
        $class = static::class;
        echo "[$this->createdAt] [$class] $message\n";
    }
}

// 使用特质
class Article {
    // 可以使用多个特质
    use Loggable, Timestampable;
    
    private string $title;
    
    public function __construct(string $title) {
        $this->title = $title;
        $this->setCreatedAt(time());
        $this->log("Article created: $title");
    }
}

$article = new Article("PHP8 Features");
echo "Created at: " . $article->getCreatedAt();

在Timestampable中也加入log方法,此时调用log就会报错,因为不知道用哪个log。

可以在使用特质的时候改成

use Loggable, Timestampable{
  Timestampable::log insteadof Loggable;
}

就会使用Timestampable中的log方法了

类与接口的最佳实践

  1. 单一职责原则:一个类应该只负责一项功能,使类更加简洁和易于维护。
  2. 面向接口编程:依赖接口而非具体实现,提高代码的灵活性和可扩展性。
  3. 合理使用访问修饰符:根据需要选择合适的访问级别,隐藏内部实现细节,只暴露必要的接口。
  4. 优先使用组合而非继承:通过组合多个类的功能来实现复杂功能,而不是多层继承。
  5. 使用类型提示:为方法参数和返回值添加类型提示,提高代码的可读性和健壮性。
  6. 避免深度继承:继承层次过深会导致代码复杂,难以理解和维护。