通过接口实现多态
本课属于 Coddy PHP 学习旅程的“面向对象编程”单元。第 34 课,共 91 课。
在上一课中,你通过继承了解了多态,子类会重写父类的方法。接口提供了另一种实现多态的强大方式,尤其适用于具有共同特征但彼此无关的类。
使用接口时,多态性来自接口本身定义的契约。任何实现接口的类都保证具有某些方法,无论其继承层次结构如何:
<?php
interface Notifiable {
public function send(string $message);
}
class EmailNotifier implements Notifiable {
public function send(string $message) {
return "Email: " . $message;
}
}
class SMSNotifier implements Notifiable {
public function send(string $message) {
return "SMS: " . $message;
}
}
class PushNotifier implements Notifiable {
public function send(string $message) {
return "Push: " . $message;
}
}
function notify(Notifiable $notifier, string $message) {
return $notifier->send($message);
}
echo notify(new EmailNotifier(), "Hello") . "\n";
echo notify(new SMSNotifier(), "Hello") . "\n";
echo notify(new PushNotifier(), "Hello");
输出:
Email: Hello
SMS: Hello
Push: Hellonotify() 函数适用于任何 Notifiable 对象。这些类没有共同的父类,它们彼此完全无关,只是实现了相同的接口。这就是它相较于基于继承的多态的主要优势:你可以根据类的能力,而不是它们的继承关系,将彼此无关的类归为一组。
关键点:基于接口的多态允许根据共同的行为互换使用彼此无关的类,让你的代码更加灵活,并与具体实现解耦。
挑战
简单让我们构建一个存储系统,展示基于接口的多态。你将创建不同的存储后端,一个文件系统和一个数据库。它们除了都实现同一个存储接口外,没有任何共同点。一个函数可以处理任何存储类型,展示接口如何让完全无关的类实现多态。
你将把代码分成 4 个文件:
Storable.php:定义一个Storable接口,其中包含两个方法签名:save($key, $data)和retrieve($key)。这个约定确保任何存储系统都能存储和检索数据,无论其内部实现方式如何。FileStorage.php:创建一个实现Storable的FileStorage类。引入接口文件。这个类应有一个私有的$directory属性,并通过构造函数设置。实现save($key, $data),使其返回"Saving '[data]' to file [directory]/[key].txt"。实现retrieve($key),使其返回"Reading from file [directory]/[key].txt"。DatabaseStorage.php:创建一个同样实现Storable的DatabaseStorage类。引入接口文件。这个类应有一个私有的$tableName属性,并通过构造函数设置。实现save($key, $data),使其返回"Inserting '[data]' into table [tableName] with key [key]"。实现retrieve($key),使其返回"Selecting from table [tableName] where key = [key]"。main.php:引入两个存储文件。创建一个名为storeData的函数,它接受一个Storable参数、一个键和数据。这个函数应返回使用键和数据调用save()的结果。再创建一个名为fetchData的函数,它接受一个Storable参数和一个键,并返回调用retrieve()的结果。
你会收到 4 个输入:一个目录路径、一个表名、一个键,以及一些要存储的数据。使用目录创建一个 FileStorage,使用表名创建一个 DatabaseStorage。先用文件存储,再用数据库存储,调用 storeData() 函数保存数据。将每个结果分别打印在单独一行。然后使用 fetchData() 从文件存储中检索数据,并打印结果。
注意,storeData() 和 fetchData() 对这两种存储类型的处理方式完全相同。它们只关心对象是否实现了 Storable。文件系统和数据库的内部实现完全不同,但通过接口约定,它们可以互换使用。
亲手试试
<?php
require_once 'FileStorage.php';
require_once 'DatabaseStorage.php';
// 读取输入
$directory = trim(fgets(STDIN));
$tableName = trim(fgets(STDIN));
$key = trim(fgets(STDIN));
$data = trim(fgets(STDIN));
// TODO: 创建一个函数 storeData,接收 Storable、key 和 data
// 它应返回用 key 和 data 调用 save() 的结果
// TODO: 创建一个函数 fetchData,接收 Storable 和 key
// 它应返回用 key 调用 retrieve() 的结果
// TODO: 用 directory 创建一个 FileStorage 实例
// TODO: 用表名创建一个 DatabaseStorage 实例
// TODO: 使用 storeData() 将数据保存到文件存储中,然后打印结果
// TODO: 使用 storeData() 将数据保存到数据库存储中,然后打印结果
// TODO: 使用 fetchData() 从文件存储中读取数据,然后打印结果
?>这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: PHP 在线编译器