[TOC] #### 1. interface 介绍 --- 在 PHP 中,interface(接口)是一种非常重要的面向对象特性,用来定义一组 “规范”(方法声明),而不包含具体实现 什么是 interface(接口)? + 简单理解一句话:接口 = 方法规范(只定义,不实现) 如何定义和实现接口 ? + 定义接口:使用 `interface` 关键字。接口里的方法都是空的(没有方法体),且默认都是 `public`(公开的) + 实现接口:类使用 `implements` 关键字实现接口,就必须把接口里规定的所有方法都写出来,否则程序会报错 ```php // 定义一个接口 Logger,它规定:实现该接口的类必须有一个 log 方法,但不关心该方法具体内容 interface Logger { public function log(string $message); } // 实现接口(文件日志驱动) class FileLogger implements Logger { // 必须实现接口中的所有方法 public function log(string $message) { file_put_contents(__DIR__ . '/1.txt', $message . PHP_EOL, FILE_APPEND); } } // 使用它 (new FileLogger)->log('hello world !'); ``` #### 2. interface 核心规则 --- interface 的核心特性与规则 + 接口继承:接口也可以通过 `extends` 关键字来继承其它接口 + 接口常量:接口中可以定义常量,通过 `const` 关键字声明,默认是 `public`(公开的)、`static`(静态的) + 多接口实现:PHP 的类是单继承的,但是一个类可以实现多个接口,用逗号隔开即可 + 接口不能实例化 ##### 接口继承 ```php interface Logger { public function log(string $message); } // 接口继承 interface WriteLogger extends Logger { public function write(string $message); } class FileLogger implements WriteLogger { public function log(string $message) {} public function write(string $message) {} } ``` ##### 接口常量 接口中定义的常量可以在实现该接口的类中直接访问,主要作用是共享常量,但有以下注意事项: + 访问控制:接口中的常量通常只使用 `const` 关键字声明为 `public` 和 `static` + 常量必须是静态的:接口中定义的常量默认是静态的,不能在接口中定义非静态的常量,否则将会抛出错误 + 不能在接口中修改常量的值:不能在实现类中修改这个常量的值,常量的值必须在接口定义时就确定 ```php interface MyInterface { const VERSION = '1.0'; const MAX_LIMIT = 100; } class User implements MyInterface { public function show() { // 使用常量 echo MyInterface::VERSION; } } ``` ##### 多接口实现 ```php interface SmsInterface { public function write(string $message); } interface LoggerInterface { public function log(string $message); } // 多接口实现 class FileLogger implements SmsInterface, LoggerInterface { public function log(string $message) {} public function write(string $message) {} } ``` #### 3. 为什么要用 interface --- 接口的最大作用在于解耦和多态,它让代码不依赖于具体的某个类,而是依赖于一个标准,使代码更加灵活、易于维护扩展 场景举例:依赖注入与多态 假如你的项目中需要记录日志,一开始用的是文件记录(FileLogger)。如果没有使用接口,你的业务代码会死死绑定在 FileLogger 上。如果以后想换成数据库记录 DatabaseLogger,就需要修改大量业务代码。 有了接口,情况就完全不同了,结合依赖注入与多态: ```php // 定义日志接口 interface LoggerInterface { public function log(string $message); } // 实现方式 1:记录到文件 class FileLogger implements LoggerInterface { public function log(string $message) { // 写入文件的逻辑... file_put_contents('app.log', $message . PHP_EOL); } } // 实现方式 2:记录到数据库 class DatabaseLogger implements LoggerInterface { public function log(string $message) { // 写入数据库的逻辑... echo "写入数据库: $message\n"; } } // 业务类:它只依赖 LoggerInterface 接口,不关心具体是谁来实现 class UserService { private LoggerInterface $logger; // 通过构造函数注入任意实现了 LoggerInterface 的对象 public function __construct(LoggerInterface $logger) { $this->logger = $logger; } public function register() { // 业务逻辑... $this->logger->log('用户注册成功!'); } } // --- 实际使用 --- // 当前使用文件记录 $fileLogger = new FileLogger(); $service = new UserService($fileLogger); $service->register(); // 以后想换成数据库记录,只需要换一行代码注入不同的对象即可,UserService 内部完全不用改! $dbLogger = new DatabaseLogger(); $service = new UserService($dbLogger); $service->register(); ``` 同样,也可以用于开发支付系统: ```php // 定义一个支付接口(标准) interface Payment { public function pay(float $amount); } // 支付宝支付(实现标准) class Alipay implements Payment { public function pay(float $amount) { echo "使用支付宝支付了:{$amount} 元\n"; } } // 微信支付(也实现同一个标准) class WechatPay implements Payment { public function pay(float $amount) { echo "使用微信支付了:{$amount} 元\n"; } } // 你的订单处理类(只认接口,不认具体是谁) class Order { private Payment $paymentMethod; // 这里声明:我只需要一个能“支付”的东西 public function __construct(Payment $paymentMethod) { $this->paymentMethod = $paymentMethod; } public function checkout(float $amount) { $this->paymentMethod->pay($amount); // 调用支付方法 } } // --- 实际使用 --- // 如果用户想用支付宝 $order1 = new Order(new Alipay()); $order1->checkout(100.00); // 输出:使用支付宝支付了:100 元 // 如果用户想用微信,你的 Order 类完全不用改,直接换注入的对象就行! $order2 = new Order(new WechatPay()); $order2->checkout(200.00); // 输出:使用微信支付了:200 元 ``` 总结:接口通过制定统一的规范,让不同的类能够被互换使用,极大地提高了代码可维护性、可扩展性以及可测试性 #### 4. 接口和抽象类对比 --- 接口(interface)和抽象类(abstract class)是 PHP 面向对象编程中一对非常容易混淆的 “好兄弟” 用一个表格快速理清它们的核心区别: | 特性 | 接口(Interface) | 抽象类(Abstract Class) | | ------------ | ------------ | ------------ | | 核心概念 | “能做什么”,是一种行为契约、标准规范 | “是什么”,是一种行为模板 | | 关键字 | 使用 implements 实现 | 使用 extends 继承 | | 继承数量 | 一个类可以实现多个接口 | 一个类只能继承一个抽象类 | | 访问控制 | 方法默认且只能是 public | 方法可以是 public、protected、private | 实际开发中应该怎么选 ? + 记住一个最简单的原则:接口定义 “标准”,抽象类提供 “复用” 什么时候用接口 ? + 当需要为完全不同的类制定一个统一的行为规范时使用接口 + 场景举例:不管是 “鸟”、“飞机”、“超人”,它们都能飞,那么就可以定义一个 `Flyable` 接口,让它们都去实现 + 核心作用:解耦。就像之前学习的支付系统,不管是支付宝还是微信,只要实现了 `Payment` 接口,订单类就能统一调用,完全不关心具体是谁 什么时候用抽象类 ? + 当需要共享一部分相同的代码或属性时使用抽象类 + 场景距离:“卡车”、“汽车” 都有 “轮胎”、“引擎” 这些共同的属性,也都有 “启动引擎” 等完全一样的逻辑。这是可以写一个抽象类,把公共的属性和启动逻辑写好,让汽车和卡车类去继承 + 核心作用:代码复用。把子类共有的逻辑抽出来放在父类里,避免重复写代码 最佳搭档:接口和抽象类组合使用 + 在成熟的 PHP 框架或大型项目中,接口和抽象类经常是组合出拳的。接口定义标准,抽奖类提供基础实现 来看一个非常经典的实战搭配: + `Notifier` 保证了无论是发邮件还是发短信,对外调用的方法都是 `send()` + `BaseNotifier` 抽象类帮所有子类处理了共同的 `$apiKey` 初始化和 `log()` 日志记录,避免了代码重复 + `EmailNotifier` 和 `SmsNotifier` 只需要专注写自己最核心的发送逻辑即可 ```php // 1. 先定义一个接口,确立“通知”的标准(能做什么) interface Notifier { public function send(string $message); } // 2. 再定义一个抽象类,实现接口,并提供一些公共的基础逻辑(代码复用) abstract class BaseNotifier implements Notifier { protected string $apiKey; public function __construct(string $apiKey) { $this->apiKey = $apiKey; // 所有通知方式都需要 API 密钥,在这里统一初始化 } // 提供一个公共的记录日志方法,供子类复用 protected function log(string $message) { echo "[日志] " . $message . PHP_EOL; } } // 3. 具体的实现类,继承抽象类,只关心自己特有的发送逻辑 class EmailNotifier extends BaseNotifier { public function send(string $message) { // 复用父类的属性和方法 $this->log("准备使用密钥 {$this->apiKey} 发送邮件..."); echo "邮件已发送:{$message}\n"; } } class SmsNotifier extends BaseNotifier { public function send(string $message) { $this->log("准备使用密钥 {$this->apiKey} 发送短信..."); echo "短信已发送:{$message}\n"; } } // --- 实际使用 --- $email = new EmailNotifier('EMAIL_KEY_123'); $email->send('您的验证码是 8888'); ``` 光看不练假把式,下面准备了3道循序渐进的实战练习题,建议先自己写一写,然后对照后面的答案,看看思路是否一致 练习1:基础接口实现(动物叫声) + 定义一个名为 `Speakable` 的接口,里面包含一个 `speak()` 方法。 + 创建 `Dog`(狗)和 `Cat`(猫)两个类,都去实现这个接口。 + `Dog` 的 `speak` 方法输出 "汪汪汪",`Cat` 的 `speak` 方法输出 "喵喵喵" 练习2:接口与多态(形状面积计算) + 定义一个 `Shape`(形状)接口,包含一个 `getArea()`(获取面积)的方法。 + 创建 `Rectangle`(长方形)类实现该接口,在构造函数中接收长和宽,并计算面积。 + 创建一个普通函数 `printArea(Shape $shape)`,传入任意形状对象,打印出它的面积 练习3:抽象类与接口的组合(员工系统) + 定义一个 `Workable` 接口,包含 `work()` 方法。 + 定义一个 `Employee` 抽象类,包含 `name` 属性和 `getName()` 方法。让这个抽象类去实现 `Workable` 接口。 + 创建 `Developer`(程序员)类继承 `Employee` 抽象类,实现 `work` 方法,输出 "[名字] 正在写代码" 练习1 参考代码: ```php interface Speakable { public function speak(); } class Dog implements Speakable { public function speak() { echo "汪汪汪\n"; } } class Cat implements Speakable { public function speak() { echo "喵喵喵\n"; } } (new Dog)->speak(); // 输出:汪汪汪 (new Cat)->speak(); // 输出:喵喵喵 ``` 练习2 参考代码: ```php interface Shape { public function getArea(); } class Rectangle implements Shape { private $width; private $height; public function __construct($width, $height) { $this->width = $width; $this->height = $height; } public function getArea() { return $this->width * $this->height; } } // 多态的体现:函数只认接口,不认具体是哪个类 function printArea(Shape $shape) { echo '面积:' . $shape->getArea(); } printArea(new Rectangle(3, 7));// 面积是:21 ``` 练习3 参考代码: ```php interface Workable { public function work(); } abstract class Employee implements Workable { protected $name; public function __construct($name) { $this->name = $name; } public function getName() { return $this->name; } } class Developer extends Employee { public function work() { echo $this->getName() . " 正在写代码\n"; } } $dev = new Developer("张三"); $dev->work(); // 输出:张三 正在写代码 ```