接口(interface)的使用

<?php
declare(strict_types=1);
ini_set('display_errors', 'On');
error_reporting(-1);

/**
 * 接口Ability1
 */
interface Ability1
{
    public const ABILITY1_NAME = '技能1'; // 接口不能定义变量,但能定义常量

    public function dig(): void;
}

/**
 * 接口Ability2
 */
interface Ability2
{
    public const ABILITY2_NAME = '技能2'; // 接口不能定义变量,但能定义常量

    public function climb(): void;
}

/**
 * 兔子类(只实现Ability1接口)
 */
class Rabbit implements Ability1
{
    public function dig(): void
    {
        echo '兔子打洞' . PHP_EOL;
    }
}

/**
 * 猴子类(只实现Ability2接口)
 */
class Monkey implements Ability2
{
    public function climb(): void
    {
        echo '猴子爬树' . PHP_EOL;
    }
}

/**
 * 鼯鼠类(同时实现Ability1接口和Ability2接口)
 */
class Rodentia implements Ability1, Ability2
{
    public function dig(): void
    {
        echo '鼯鼠打洞' . PHP_EOL;
    }

    public function climb(): void
    {
        echo '鼯鼠爬树' . PHP_EOL;
    }
}

echo Ability1::ABILITY1_NAME . PHP_EOL; // 技能1
echo Ability2::ABILITY2_NAME . PHP_EOL; // 技能2

$rabbit = new Rabbit();
$rabbit->dig(); // 兔子打洞

$monkey = new Monkey();
$monkey->climb(); // 猴子爬树

$rodentia = new Rodentia();
$rodentia->dig();   // 鼯鼠打洞
$rodentia->climb(); // 鼯鼠爬树

//========== 总结 ==========//
// 1、接口里定义的方法都必须是public,且不能写方法体(即便是空方法体{}也不行),这是接口的特性。
// 2、接口可以继承接口,并且和类继承类一样都是用extends关键字,类在实现子接口时也必须实现父接口的方法。
// 3、类如果要实现接口则必须实现该接口定义的全部方法,只要有一个方法未实现都会报“Fatal error”。
// 4、接口不能包含成员变量(属性),但能包含常量(使用const关键字定义,但不建议在接口里定义常量),并且不同接口可以包含同名常量,
//    接口常量使用格式为“接口名::常量名”,如:Ability1::ABILITY1_NAME,
// 5、和大多数面向对象编程语言一样,PHP也是不支持多继承,但可以实现多个接口,格式为“class 类名 implements 接口1, 接口2, 接口3”。
//    需要注意的是实现多个接口时接口之间不能有同名常量,否则会报“Fatal error”。



<?php
declare(strict_types=1);
ini_set('display_errors', 'On');
error_reporting(-1);

/**
 * 打印变量的相关信息
 *
 * @param mixed $value 要打印的表达式
 * @param mixed ...$values 更多要打印的表达式
 * @return void echo
 */
function v(mixed $value, mixed ...$values): void
{
    ob_start(); // 打开输出控制缓冲
    var_dump($value);
    echo ob_get_clean(); // 从缓冲区获取var_dump()的内容,然后清空缓冲区

    foreach ($values as $v) {
        v($v); // 递归
    }
}

/**
 * Ability1接口
 */
interface Ability1
{
    public function swim(): void;
}

/**
 * Ability2接口
 */
interface Ability2
{
    public function drive(): void;
}

/**
 * Ability3接口
 */
interface Ability3
{
    public function program(): void;
}

/**
 * Human类
 */
class Human implements Ability1, Ability2
{
    public string $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function swim(): void
    {
        echo "俺({$this->name})学会游泳辣~~~";
    }

    public function drive(): void
    {
        echo "俺({$this->name})学会开车辣~~~";
    }
}

$human = new Human('张三');

v($human::class); // string(5) "Human"
v(gettype($human)); // string(6) "object"
v(get_class($human)); // string(5) "Human"
v($human instanceof Human); // bool(true)
v($human instanceof Ability1); // bool(true)
v($human instanceof Ability2); // bool(true)
v($human instanceof Ability3); // bool(false)
v($human instanceof PDO); // bool(false)


//========== 总结 ==========//
// 1、接口虽然不能实例化,但是也能用instanceof关键字判断一个类实例是否实现了接口。



<?php
declare(strict_types=1);
ini_set('display_errors', 'On');
error_reporting(-1);

/**
 * 打印变量的相关信息
 *
 * @param mixed $value 要打印的表达式
 * @param mixed ...$values 更多要打印的表达式
 * @return void echo
 */
function v(mixed $value, mixed ...$values): void
{
    ob_start(); // 打开输出控制缓冲
    var_dump($value);
    echo ob_get_clean(); // 从缓冲区获取var_dump()的内容,然后清空缓冲区

    foreach ($values as $v) {
        v($v); // 递归
    }
}

/**
 * 测试将函数的形参和返回值都声明为接口类型
 *
 * @param Ability $ability Ability接口(重要说明:形参允许声明为接口类型)
 * @return Ability Ability接口(重要说明:返回值允许声明为接口类型)
 */
function foo(Ability $ability): Ability
{
    // 重要说明:输入“$ability->”的时候,PhpStorm只会提示Ability接口的三个方法,而不会提示Human类的name属性,
    //           必须使用instanceof进行判断,确保$ability是Human类实例PhpStorm才会提示name属性。
    if ($ability instanceof Human) {
        echo "[函数]俺叫$ability->name~~~" . PHP_EOL;
    }

    return $ability;
}

/**
 * Ability接口
 */
interface Ability
{
    public function swim(): void;

    public function drive(): void;

    public function program(): void;
}

/**
 * Human类
 */
class Human implements Ability
{
    public string $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function swim(): void
    {
        echo "俺({$this->name})学会游泳辣~~~" . PHP_EOL;
    }

    public function drive(): void
    {
        echo "俺({$this->name})学会开车辣~~~" . PHP_EOL;
    }

    public function program(): void
    {
        echo "俺({$this->name})学会编程辣~~~" . PHP_EOL;
    }

    public function intro(): void
    {
        echo "[方法]俺叫$this->name~~~" . PHP_EOL;
    }

}

$human = new Human('张三');

// 重要说明:虽然foo()的形参要求数据类型为Ability接口,但由于Human类实现了Ability接口,所以可以用Human类实例入参
$ability = foo($human); // [函数]俺叫张三~~~

v($ability::class); // string(5) "Human"
v(gettype($ability)); // string(6) "object"
v(get_class($ability)); // string(5) "Human"

// 重要说明:$ability既是Human类实例,又是Ability接口实例,两者并不冲突
v($ability instanceof Human); // bool(true)
v($ability instanceof Ability); // bool(true)

$ability->swim(); // 俺(张三)学会游泳辣~~~
$ability->drive(); // 俺(张三)学会开车辣~~~
$ability->program(); // 俺(张三)学会编程辣~~~
$ability->intro(); // [方法]俺叫张三~~~


//========== 总结 ==========//
// 1、函数(或方法)的形参可以声明为接口类型,并且实参必须是实现了该接口的类的实例。
// 2、函数(或方法)的返回值可以声明为接口类型,并且实际返回值必须是实现了该接口的类的实例。
// 3、无论是形参还是返回值,如果声明数据类型为接口,使用时需要特别小心,因为已经丢失了接口实现类的具体信息,也就是说开发者只知道变量
//    是某个实现了接口的类的实例,却不知道到底是什么类(即类名),所以也就无法使用类属性和类方法,如果想使用类属性和类方法就必须先使
//    用instanceof关键字确定是什么类(有点类似反射Reflection,都是运行时分析)。
// 4、正如上面所说,接口类型变量会丢失接口实现类的具体信息,开发者或许明确知道$ability变量是Human类实例,但对于PhpStorm来说并不知道,
//    所以在使用$ability变量时PhpStorm只会提示Ability接口的三个方法,而无法提示Human类的name属性,只有在使用instanceof关键字确定了
//    $ability变量是Human类实例才会提示name属性。
// 5、接口类型变量的本质始终是类实例,只是裹了一层接口的外衣,就像张三穿上马甲戴上面具也还是那个张三,只是没那么容易被认出来而已。

Copyright © 2024 码农人生. All Rights Reserved