Skip to content

6. 接口

接口是一种定义方法原型的结构。实现接口的类必须定义该接口中所有方法的代码。

6.1. 脚本树结构

Image

6.2. 第一个接口

我们在脚本 [IExemple.php] 中定义了一个接口 [IExemple]。在同一个脚本中,我们定义了两个实现该接口的类 [IExemple]


<?php

// 严格遵守函数参数类型
declare(strict_types=1);

// 命名空间
namespace Exemples;

// 一个接口
interface IExemple {
    // 接口的方法
    public function ajouter(int $i, int $j): int;
    public function soustraire(int $i, int $j): int;
}

// 接口的实现 1IExemple
class Classe1 implements IExemple {
    public function ajouter(int $a, int $b): int {
        return $a + $b + 10;
    }

    public function soustraire(int $a, int $b): int {
        return $a - $b + 20;
    }

}

// 接口的第二个实现 IExemple
class Classe2 implements IExemple {
    public function ajouter(int $a, int $b) : int{
        return $a + $b + 100;
    }

    public function soustraire(int $a, int $b) : int {
        return $a - $b + 200;
    }

}

注释

  • 第 10-14 行:接口 IExemple 定义了两个方法:函数 ajouter(第 12 行)和函数 soustraire(第 13 行)。 该接口并未定义这些方法的代码。具体实现将由实现该接口的类来完成;
  • 第 17 行:类 Classe1 实现了接口 IExemple。 因此,它为方法 ajouter(第 18 行)和 soustraire(第 22 行)定义了实现代码;
  • 第 29-38 行:Classe2 类亦同

我们在以下脚本 [interfaces-01.php] 中使用接口 [IExemple]


<?php

require_once "IExemple.php";
use \Exemples\Classe1;
use \Exemples\Classe2;
use \Exemples\IExemple;

// 函数
function calculer(int $a, int $b, IExemple $interface) : void {
  print $interface->ajouter($a, $b)."\n";
  print $interface->soustraire($a, $b)."\n";
}

// ------------------- main
// 创建 2 个类型为 Classe1 和 Classe2 的对象
$c1 = new Classe1();
$c2 = new Classe2();
// 调用calculer函数
calculer(4, 3, $c1);
calculer(14, 13, $c2);

注释

  • 第 3-6 行:包含并定义脚本所使用的类和接口;
  • 第 3 行:此处未使用 include 语句,而是使用了 require_once 语句。 include语句会将文件包含到当前脚本中,即使该文件已被包含过;而require_once语句仅包含一次。因此,在代码包含方面,我们更倾向于使用require_once语句;
  • 第 9-12 行:定义了一个函数;
  • 第 9 行:此处声明了三个参数的类型。若在执行时,实际参数的类型与预期不符,则会抛出错误。第三个参数的类型为 [IExemple]。 这意味着实际参数必须是实现 [IExemple] 接口的类的一个实例,因此在本例中,应为 [Classe1] [Classe2] 类的实例;
  • 第 10-11 行:我们使用了 [IExemple] 接口中的 [ajouter, soustraire] 方法;
  • 第 19 行:实际的第 3 个参数类型为 [Classe1],与函数第 3 个形式参数的类型 [IExemple] 兼容;
  • 第 20 行:[Classe2] 类亦同;

结果

1
2
3
4
17
21
127
201

6.3. 接口的派生

正如子类可以继承父类一样,子接口也可以继承父接口。

考虑在文件 [IExemple2.php] 中定义的接口 [IExemple2]


<?php

// 严格遵守函数参数的类型
declare(strict_types=1);
// 命名空间
namespace Exemples;

// 包含接口 IExemple 的定义
require_once __DIR__."/IExemple.php";

// 一个从 IExemple 派生的接口
interface IExemple2 extends IExemple {
    // 接口的方法
    public function multiplier(int $i, int $j): int;
}

// IExemple2 接口的实现
class Classe3 extends Classe2 implements IExemple2 {
   
    public function multiplier(int $a, int $b): int {
        return $a * $b;
    }

}

注释

  • 第 6 行:接口 [IExemple2] 将与接口 [Iexemple] 位于同一命名空间中;
  • 第 9 行:引入接口 [Iexemple] 的定义文件;
  • 第12-15定义接口[IExemple2],该接口继承自接口[Iexemple],并为其添加了一个新方法(第14行);
  • 第 18-24 行:类 [Classe3] 继承自类 [Classe2]。 由于后者实现了接口 [IExemple],因此类 [Classe3] 也将实现该接口。 实际上,[Classe3] 继承了 [Classe2] 类的所有 public 和 protected 方法,因此也包含加法和减法方法。 此外,我们注意到 [Classe3] 实现了接口 [IExemple2]。因此,它也必须实现乘法方法,而 [Classe2] 并未实现该方法。第 20-22 行代码正是为此而设计的;

接口 [IExemple2] 由以下脚本 [interfaces-02] 调用:


<?php

// 严格遵守函数参数类型
declare(strict_types=1);

// 脚本所需类和接口的引入及修饰
require_once __DIR__."/Iexemple2.php";
use \Exemples\IExemple2;
use \Exemples\Classe3;

// 函数
function calculer(int $a, int $b, IExemple2 $interface) {
    print $interface->ajouter($a, $b) . "\n";
    print $interface->soustraire($a, $b) . "\n";
    print $interface->multiplier($a, $b) . "\n";
}

// ------------------- main
// 创建一个实现 IExemple2 的 Classe3 类型的对象
$c3 = new Classe3();
// 调用calculer函数
calculer(4, 3, $c3);

注释

  • 第 12 行:函数 [calculer] 的第 3 个参数类型为 [IExemple2]
  • 第 13-15 行:使用了 [IExemple2] 接口的三个方法;
  • 第22行:调用函数[calculer],其第3个参数为类型为[IExemple2]的对象(第20行);

结果

1
2
3
107
201
12

6.4. 将接口作为函数参数传递

我们在上一节中看到,当函数参数的预期类型为类时,实际参数可以是预期类型或其派生类型的对象。对于接口也是如此,如下面的脚本 [interfaces-03.php] 所示:


<?php

// 严格遵守函数参数的类型
declare(strict_types=1);

// 包含并限定脚本所需的类和接口
require_once __DIR__."/IExemple.php";
use \Exemples\IExemple;
require_once __DIR__."/IExemple2.php";
use \Exemples\Classe3;

// 函数
function calculer(int $a, int $b, IExemple $interface) {
  print $interface->ajouter($a, $b) . "\n";
  print $interface->soustraire($a, $b) . "\n";
}

// ------------------- main
// 创建一个类型为 Classe3 的对象,该对象实现了 IExemple2,因此也实现了 IExemple
$c3 = new Classe3();
// 调用函数calculer
calculer(4, 3, $c3);

注释

  • 第 13 行,函数 [calculer] 期望第三个参数为类型 [IExemple]。这意味着实际参数的类型可以是 [IExemple] 或其派生类型
  • 第 20 行:实例化了一个类型为 [Classe3] 的 $c3 对象,该类实现了 [IExemple2] 接口,而该接口本身又继承了 [IExemple] 接口。 因此,最终 $c3 实现了 [IExemple] 接口;
  • 第22行:调用函数[calculer],其第三个参数是一个类型为[Classe3]的$c3对象。 通过类继承和实现机制,我们看到 $c3 对象实现了 [IExemple] 类型。因此,我们可以将其用作第三个参数;

结果

107
201