0%

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

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

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

类的基本概念与定义

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

1. 基本类定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?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:私有成员,只能在类内部访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?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 引入了构造方法属性提升,允许在构造方法参数中直接声明类属性,简化代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?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 关键字,用于声明只能初始化一次且不能修改的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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 支持联合类型,表示一个参数或返回值可以是多种类型中的一种。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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 支持命名参数,允许在调用方法时通过参数名指定值,而不必严格按照参数顺序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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 关键字实现类的继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?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 关键字声明,不能被实例化,只能被继承。抽象方法没有实现,必须在子类中实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?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. 基本接口定义与实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?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 关键字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?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)。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?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. 访问静态属性和静态方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?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 单继承的限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?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();
?>

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 定义特质
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。

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

1
2
3
use Loggable, Timestampable{
Timestampable::log insteadof Loggable;
}

就会使用Timestampable中的log方法了

类与接口的最佳实践

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

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10