类和对象:三种特殊类

上一节我们学习了面向对象的基本概念,了解了面向对象的三个特性:

  • 继承
    • 子类可继承父类的所有可访问方法及属性,并且子类可自我扩展(拥有自己的属性和方法)
  • 封装
    • 利用访问控制publicprotectedprivate)进行数据细节的隐藏
  • 多态
    • 通过实现接口,相同方法在不同对象上具有不同的结果

复习完毕,这一节我们将学习三种特殊的类

在 PHP 的类中,有三种类比较特别,它们分别是:抽象类、final 类和匿名类。

抽象类

抽象类不能被实例化,因为抽象类并不完整。抽象类只规定这个类“是什么”,部分或全部操作的实现延迟到子类中,故而无法被实例化。

抽象类使用关键字abstract进行声明,语法如下:

abstract class AbstractClass
{
    // 类定义
}

抽象类中可能包含抽象方法,不能被实例化,其余与普通类一致。

抽象方法

抽象方法也不提供具体的实现。

含有抽象方法的类必须被定义为抽象类(抽象方法本就不完整),而抽象类不一定要包含抽象方法。如下代码:

<?php

// 抽象类

abstract class AbstractClass
{
    public $prop = '没有抽象属性的概念';
    public $str = '只有抽象类和抽象方法';

    // 抽象类和抽象方法使用关键字 abstract 声明
    // 并且抽象方法没有具体实现,故而只需声明方法,而没有实现代码块(花括号)
    abstract public function abstractFunc();

    public function notAbstractFunc()
    {
        echo '抽象类也可以包含非抽象方法',"\n";
    }
}
// 测试:
$abst = new AbstractClass();
var_dump($abst);// Fatal error:  Uncaught Error: Cannot instantiate abstract class

class NotAbstract
{
    public $prop = '尝试声明一个含有抽象方法但没有使用关键字abstract声明的类';
    abstract public function abstractFunc();
}
// 测试:
$abst = new NotAbstract();
var_dump($abst);// Fatal error: Class NotAbstract contains 1 abstract method and must therefore be declared abstract or implement the remaining methods

继承一个抽象类时,子类必须实现父类中的所有抽象方法,否则子类也必须被定义为抽象类(因为该类中还存在未被实现的抽象方法)。

final类

final类表明该类不允许被继承,使用关键字final声明,语法如下:

final class FinalClass
{
    // 类定义
}

继承一个final类将会造成错误:

<?php

// final 类

final class Subclass {}

class Ordinary extends Subclass {}

// 测试:
$ordinary = new Ordinary();
var_dump($ordinary);// Fatal error: Class Ordinary may not inherit from final class (Subclass)

同时,final关键字也可以定义方法。

final 方法

final方法表示该方法不能被子类覆盖(不能在子类重写该方法),示例如下:

class Base
{
    final public function finalFunc()
    {
        echo '我是一个无法被子类重写的顽固派',"\n";
    }
}

class Subclass extends Base
{
    public function finalFunc()
    {
        echo '我试图重写父类的final方法',"\n";
    }
}

$sub = new Subclass();
$sub->finalFunc();// Fatal error: Cannot override final method Base::finalFunc()

final关键字声明的类或方法通常用于不希望有任何改变的情况,就像它的名字,“最终的”,最终方案是不希望再修改的。

匿名类

最后一个特殊类是匿名类。它可以创建一次性的简单对象。语法:

new class {
    // 类定义
}

请看下面示例:

<?php

// 匿名类

function anonymous1()
{
    return new class {};// 返回一个空的匿名类
}

function anonymous2()
{
    return new class {
        public $prop = '匿名类的一个public属性';
        private $_prop = '匿名类的一个private属性';
    };
}

$anon1 = anonymous1();
var_dump($anon1);
/* 输出:
object(class@anonymous)#1 (0) {
}
*/

$anon2 = anonymous2();
var_dump($anon2);
/* 输出:
object(class@anonymous)#2 (2) {
  ["prop"]=>
  string(30) "匿名类的一个public属性"
  ["_prop":"class@anonymous":private]=>
  string(31) "匿名类的一个private属性"
}
*/

匿名类可以继承其他类、实现接口以及使用 Trait。但如果匿名类嵌套在普通类中,在匿名类中如果要访问这个普通类的受保护的(protected)或者私有(private)属性,则必须将该属性传入匿名函数的构造函数,否则无法访问。下方的示例代码构造了一个简单的消息传递类,当一条消息发出时,匿名函数处理这条消息。

<?php

class Chat
{
    public $name;
    private $msg;

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

    // 处理发来的消息:阅读,并提示“已阅”
    public function read()
    {
        return new class($this->name, $this->msg) extends Chat {
            // 外部类的私有属性必须使用构造函数传递后才能使用
            public function __construct($name, $msg)
            {
                $this->name = $name;
                $this->msg = $msg;
            }
            public function prompt()
            {
                echo $this->name, '发来消息:',$this->msg, PHP_EOL;
            }
        };
    }
}

// 测试:
$user = new Chat('小明', '你好');
$user->read()->prompt();// 输出:小明发来消息:你好

上方示例代码中,类Chat的公开属性$name在匿名函数中也传递给了其构造函数,这是因为如果不传递,该匿名类继承Chat类,$name的值将被初始化为NULL

匿名函数适合这种仅用一次的类定义。类似的还有日志打印等等。


本节中我们讲解了 PHP 中类与对象的三种特殊类:抽象类、final 类和匿名类,分别阐述了它们的特点与使用场景,应该根据其特点选择使用合适的类。

下一节我们进入接口的学习!