类和对象

上一节我们了解了类和对象中三种特殊的类:

  • 抽象类:含有抽象方法(不提供具体内容)的类,使用关键字abstract定义,无法被实例化
  • final类:不允许被继承的类,关键字final
  • 匿名类:可一次性创建简单对象的类,适用于仅用一次的类定义

这一节我们将了解类与对象中的接口

接口

这里的接口不是我们惯常说的数据线接口,而是一组方法的定义

为什么这么说?因为接口只负责指定方法,而不用负责实现(是个大猪蹄子没错了)。

有心的朋友可能发现,这不是和抽象方法一样吗?对,接口方法除了不需要使用关键字abstract声明以及接口方法必须被声明为public之外,接口方法与抽象方法一样。

实现一个接口就必须实现这个接口内定义的所有方法。接口声明如下:

interface iInterfaceName {
    public function interfaceFunc();
    const CONSTANCE = '接口也可定义常量,常量不可被覆盖';
}

声明接口使用关键字interface,后跟接口名。如上声明所见,接口也可以定义常量,同样使用关键字const,并且不可被覆盖。

继承接口

接口之间可以像普通类一样通过关键字extends继承,如下所示:

<?php

interface iBuy
{// 购买接口
    public function add_shopping_cart(Product $product);
    public function create_order(array $array);
}
interface iPay extends iBuy
{// 支付接口
    public function pay();
}

接口只涉及方法定义(而不涉及对象),不同于类只能继承一个类,一个接口可以继承多个接口。继承时使用逗号(英文输入法下)连接:

<?php

interface iPerimeter
{// 求周长的接口
    public function perimeter();
}

interface iArea
{// 求面积的接口
    public function area();
}

interface iGraphic extends iPerimeter, iArea
{
    // 继承多个接口
}

实现接口

接口只负责定义方法,类则必须实现它们。类实现接口的关键字为implements,一个类可实现多个接口,使用逗号进行连接:

<?php

interface iPerimeter
{// 求周长的接口
    public function perimeter();
}

interface iArea
{// 求面积的接口
    public function area();
}

class Rectangle implements iPerimeter, iArea
{
    public function __construct($width, $length)
    {
        $this->width = $width;
        $this->length = $length;
    }

    // 实现接口中定义的方法
    public function perimeter()
    {
        return ($this->length + $this->width) * 2;
    }
    public function area()
    {
        return $this->length * $this->width;
    }

}

// 测试:
$rect = new Rectangle(2, 3);
$peri = $rect->perimeter();
echo '矩形周长:',$peri;// 输出:矩形周长:10

$area = $rect->area();
echo '矩形面积:',$area, PHP_EOL;// 输出:矩形面积:6

要注意的是:

  • 实现多个接口时,接口内的方法不允许重名
  • 实现接口方法时,类方法定义必须与接口所定义的一致(即方法名、可见性和参数个数等)

使用接口常量

前面已说过,接口也可以定义常量,实现接口时,常量也不允许重名(即常量不能被覆盖)。

使用接口常量时可通过“接口名”加范围解析操作符(还记得吗?在类结构一章讲过的范围解析操作符,由两个冒号组成)加常量名获得,如下所示:

<?php

interface iDatabase
{
    // 定义了数据库连接常量
    const DB_TYPE = 'mysql';
    const DB_HOST = 'localhost';
    const DB_PORT = 3306;
    const DB_NAME = 'test';
}
class Database implements iDatabase
{
    public $conn;
    public function __construct()
    {
        try {
            // 使用了接口定义的常量
            $dsn = 'mysql:host=' . iDatabase::DB_HOST . ';port=' . iDatabase::DB_PORT . ';dbname=' . iDatabase::DBNAME;
            $opt = [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
            ];
            $this->conn = new PDO($dsn, 'root', '12345678', $opt);
            $this->conn->query('SET NAMES UTF8');

            echo '连接成功!';
        } catch (\Throwable $th) {
            error_log($th);
        }
        return $this;
    }
}

// 测试:
$db = new Database();// 输出:连接成功!

示例代码只简要定义了一个数据库连接类,这里不做详细探讨。

与抽象类对比

前面提到过,接口方法与抽象方法很像,但接口与抽象类有很多不同之处:

  • 接口只定义常量和接口方法,而抽象类可以包含成员变量(即类属性)和成员方法
  • 接口定义的接口方法只能声明为public,而抽象类方法没有限制
  • 接口方法不需要使用abstract关键字声明,而抽象方法和抽象类需要
  • 一个类可以实现多个接口,但类只能继承一个抽象类
  • 接口可以继承,而抽象类是为了继承而存在的(不被继承的抽象类将没有意义)

通过这些不同点,我们应该根据实际情况来选择使用接口还是抽象类。


本节我们学习了接口的定义及基本使用,并将之与上一节讲过的抽象类进行对比,下一节我们将学习另一个与类很像但又完全不同的新方法:Trait。