Skip to content

6. 接口

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

6.1. 脚本树

Image

6.2. 第一个接口

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


<?php
 
// strict adherence to function parameter types
declare(strict_types=1);
 
// namespace
namespace Exemples;
 
// an interface
interface IExemple {
    // interface methods
    public function ajouter(int $i, int $j): int;
    public function soustraire(int $i, int $j): int;
}
 
// implementation 1 of the IExemple interface
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;
    }
 
}
 
// implementation 2 of the IExemple interface
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 行:IExample 接口定义了两个方法:add 函数(第 12 行)和 subtract 函数(第 13 行)。该接口并未定义这些方法的代码,具体实现将由实现该接口的类来完成;
  • 第 17 行:Class1实现了 IExample 接口。因此,它为 add 方法(第 18 行)和 subtract 方法(第 22 行)定义了代码;
  • 第 29–38 行:Class2 类也是如此

我们在下面的 [interfaces-01.php] 脚本中使用了 [IExample] 接口:


<?php
 
require_once "IExemple.php";
use \Exemples\Classe1;
use \Exemples\Classe2;
use \Exemples\IExemple;
 
// function
function calculer(int $a, int $b, IExemple $interface) : void {
  print $interface->ajouter($a, $b)."\n";
  print $interface->soustraire($a, $b)."\n";
}
 
// ------------------- hand
// creation of 2 objects of type Class1 and Class2
$c1 = new Classe1();
$c2 = new Classe2();
// call the calculate function
calculer(4, 3, $c1);
calculer(14, 13, $c2);

注释

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

结果

1
2
3
4
17
21
127
201

6.3. 继承接口

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

请看 [IExample2.php] 文件中定义的 [IExample2] 接口:


<?php
 
// strict adherence to function parameter types
declare(strict_types=1);
// namespace
namespace Exemples;
 
// interface definition IExemple included
require_once __DIR__."/IExemple.php";
 
// an interface derived from IExemple
interface IExemple2 extends IExemple {
    // the interface method
    public function multiplier(int $i, int $j): int;
}
 
// implementation of IExemple2 interface
class Classe3 extends Classe2 implements IExemple2 {
   
    public function multiplier(int $a, int $b): int {
        return $a * $b;
    }
 
}

评论

  • 第 6 行:接口 [IExample2] 将与接口 [Iexample] 位于同一命名空间中;
  • 第 9 行:引入了 [Iexample] 接口的定义文件;
  • 第 12–15 行:定义接口 [IExample2],该接口继承自接口 [IExample],并为其添加了一个新方法(第 14 行);
  • 第 18–24 行:类 [Class3] 继承自类 [Class2]。由于 [Class2] 实现了接口 [IExample],因此 [Class3] 也将实现该接口。 实际上,[Class3] 继承了 [Class2] 类的所有 public 和 protected 方法,包括 add subtract 方法。此外,我们指定 [Class3] 实现 [IExample2] 接口。因此,它还必须实现 multiply 方法,而 [Class2] 并未实现该方法。这就是第 20–22 行所做的工作;

[IExample2] 接口在以下 [interfaces-02] 脚本中被使用:


<?php
 
// strict adherence to function parameter types
declare(strict_types=1);
 
// inclusion and qualification of classes and interfaces required by the script
require_once __DIR__."/Iexemple2.php";
use \Exemples\IExemple2;
use \Exemples\Classe3;
 
// function
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";
}
 
// ------------------- hand
// creation of 1 Class3 object implementing IExemple2
$c3 = new Classe3();
// call the calculate function
calculer(4, 3, $c3);

注释

  • 第 12 行:[calculate] 函数的第三个参数类型为 [IExample2]
  • 第 13–15 行:使用了 [IExample2] 接口的三个方法;
  • 第 22 行:调用 [calculate] 函数,并将类型为 [IExample2] 的对象作为第三个参数(第 20 行);

结果

1
2
3
107
201
12

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

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


<?php
 
// strict adherence to function parameter types
declare(strict_types=1);
 
// inclusion and qualification of classes and interfaces required by the script
require_once __DIR__."/IExemple.php";
use \Exemples\IExemple;
require_once __DIR__."/IExemple2.php";
use \Exemples\Classe3;
 
// function
function calculer(int $a, int $b, IExemple $interface) {
  print $interface->ajouter($a, $b) . "\n";
  print $interface->soustraire($a, $b) . "\n";
}
 
// ------------------- hand
// creation of 1 object of type Class3 which implements IExemple2 and therefore IExemple
$c3 = new Classe3();
// call the calculate function
calculer(4, 3, $c3);

注释

  • 第 13 行:[calculate] 函数期望其第三个参数为类型 [IExample]。这意味着实际参数类型可以是 [IExample] 其派生类型;
  • 第 20 行:我们实例化了一个类型为 [Class3] 的对象 $c3,该类实现了接口 [IExample2],而 [IExample2] 本身又继承自接口 [IExample]。因此,最终 $c3 实现了接口 [IExample]
  • 第 22 行:我们调用 [calculate] 函数,并将类型为 [Class3] 的对象 $c3 作为第三个参数传入。通过类继承与实现的相互作用,我们看到对象 $c3 实现了 [IExample] 接口。因此,我们可以将其用作第三个参数;

结果

107
201