类的种类

PHP 7 中支持多种类(class)的实现,包括普通类、抽象类、接口、特性(trait)以及 final 类和匿名类。

普通类

类以关键字 “class” 定义,后跟类名。类名不能是 PHP 预留关键字,必须以字母或下划线开头,每个类有属于自己的常量、方法、属性。类的实例叫作对象,可通过关键字 “new” 来实例化对象。

先编写一个 PHP 的普通类:

<?php

class A
{
    const B = 1;
    static $b = 2;
    var $c = 3;

    public function b()
    {
        var_dump("public");
    }

    protected function c()
    {
        var_dump("protected");
    }

    private function _d()
    {
        var_dump("private");
    }

    static function e()
    {
        var_dump(self::$b, self::B);
        //var_dump(self::$c);  //不能通过“::”访问非静态的属性
    }
}

$a = new A();
$a->b();
//$a->c(); //Fatal error: Uncaught Error: Call to protected method A::c() from context ''
//$a->d(); //Fatal error: Uncaught Error: Call to undefined method A::d()
A::e();

说明:

  1. 类的属性和方法有三个访问级别,分别为 public(公有), protected(受保护)或 private(私有)。类的外部不能直接调用 protected(受保护)或 private(私有)的方法和属性。

  2. 类的属性有普通属性和静态、常量属性,静态、常量属性则分别用关键字 “static”“const” 声明。

  3. 类的普通方法、属性被自己的成员函数调用时可使用 “$this->”,静态方法及静态属性也可通过这种方式访问。

  4. 类的常量属性、静态属性、静态方法,通过 “self::” 调用。非静态的方法也可以通过 “::” 调用,但是非静态的属性不能通过 “::” 访问。

抽象类

PHP 5 开始支持抽象类和抽象方法,抽象类不能被实例化。

下面编写一个抽象类的示例:

<?php
abstract class A
{
    abstract protected function b($name);
    abstract protected function c();
    // abstract private function c(); /*抽象方法不能设置为私有*/
    abstract  public function d();
    /*PHP Fatal error:  Abstract function A::d() cannot contain body
    abstract  public function d(){}
    */
    public function f(){
        echo 'A->d()';
    }
}

class E extends A{
    public function b($name, $name2=''){
        echo $name.$name2;
    }
    public function c(){
        echo 'pub_c';
    }
    public function d(){
        echo 'pub_d';
    }
    /*访问级别不能降低*/
    /*protected function d(){
        echo 'pri_d';
    } */
    /*参数个数也必须一致*/
    protected function b($name, $name2){
        echo 'pri_d';
    }
}

$obj = new E();
$obj->b('name');
$obj->c();
$obj->d();

说明:

  1. 抽象方法必须被子类继承实现,所以不能为私有,只能是受保护的或公有的;

  2. 抽象类的方法访问控制级别必须和子类相等或更为宽松。例如,父类的抽象方法是受保护的,子类实现时则必须为受保护的或者公有的;

  3. 抽象方法的实现,必传参数的数量及类型必须严格一致;

  4. 抽象类的非抽象方法,子类可不实现,等同于普通类方法的继承;

  5. 抽象类中的抽象方法,只能定义,不能实现其方法体;

  6. 抽象类可定义常量,且可被子类覆盖。

接口

PHP 与大多数面向对象编程语言一样,不支持多重继承,每个类只能继承一个父类。

对象接口(interface)里定义的方法子类需全部实现,且接口不能直接被实例化。接口的主要特性和需注意的点如下。

  1. 接口类可以通过 extend 继承一个或多个接口类,多个接口之间用逗号分隔,用以实现接口类的扩充。

  2. 接口类定义的方法必须声明为公有,因此子类的实现方法也只能为公有。接口方法体也必须为空。

  3. 接口类定义的常量和类常量使用方式一样,但不能被子类或者子接口覆盖。

  4. 普通类通过关键字 “implements” 来实现一个或多个接口。

  5. 继承多个接口,方法不能有重名。

  6. 普通类继承接口,必须实现接口类里面所有的方法,参数也和接口方法定义相同。可加默认参数,这点和抽象类方法的实现基本一致。

接下来,结合 PHP 代码一起来看接口的定义与实现。

(1)接口定义

interface A{
    public function demo1(); /* 接口方法不能有方法体,且只能为公有 */
    /* PHP Fatal error: Access type for interface method A::demo1() must be omitted */
    //protected function demo1();
}

(2)接口扩充

interface B{
    public function demo2();
}
interface C extends A, B{/* 只能通过extends实现接口类扩充 */
    public function demo3();
}

(3)普通类对接口的实现

class D  implements A{
    /* PHP Fatal error:  Declaration of D::demo1 ($name) must be compatible with A::demo1 () */
    //public function demo1($name){
    public function demo1($name=1) {/* 接口未定义的参数,可通过加默认值实现 */
    }
}

(4)普通类实现多个接口

class D implements A, B, C{/* 通过implements实现多个接口类,类之间通过逗号分隔 */
    /* 继承的所有接口方法都必须实现,否则报错 */
    public function demo1(){
    }
    public function demo2(){
    }
    public function demo3(){
    }
}

特性

特性(trait)从 PHP5.4.0 开始启用,便于在不同层次结构内实现代码复用。特性不能直接被实例化,主要特性和需注意的点如下。

  1. 特性与普通类相似,有自己的方法、属性等,但是不可通 extends 继承,也没有类常量。

  2. 特性的方法如果和当前类方法冲突,会被当前类的方法覆盖。如果和基类方法冲突,特性方法会覆盖基类中的方法。优先级:当前类>特性类>基类

  3. 一个类加载了多个特性,当多个特性中方法有重名时,需要在代码中通过关键字 “insteadof” 设置优先级或者通过 “as” 关键字重命名处理,否则报错。

结合 PHP 代码实践特性的定义与用法。

(1)特性的定义

trait A{                          //定义特性A
    public $b = 'public';         //定义特性的属性
    function demo1(){             //定义特性的普通方法
        echo 'A->demo1';
    }
    static function demo2(){      //定义特性的静态方法
        echo 'A::demo2';
    }
    abstract public function demo3(); //定义特性的抽象方法
}

(2)特性扩容

trait B{
    use A;                 //通过关键字use即可拥有特性A全部的方法属性
    function demo4(){
        echo  'B->demo4';
    }
}

可见,此时 B 包含了 A 的所有内容,扩容非常简单。

(3)普通类使用特性

class ChildClass {
    use B;                 /* 此时相当于把特性B所有方法打包加载进子类 */
    function demo3(){     /* 和继承类似,必须实现特性中的抽象方法 */
        echo 'trait:abstract:demo3';
    }
}

(4)普通类加载多个特性

trait C{
    function demo5(){
        echo 'C->demo5';
    }
}

(5)通过 use 加逗号加载多个特性

class ChildClass{
    use B, C;       /* 逗号隔开,加载多个特性;也可一个一个地加载 use B; use C; */
    function demo3(){
        echo 'trait:abstract:demo3';
    }
}

(6)特性方法优先级(自身>特性>基类)

class ParentClass{
    function demo1(){     /* 被特性A中demo1覆盖 */
        echo 'Parent->demo1';
    }
}
class ChildClass extends ParentClass{
    use A;
    function demo2(){     /* 自身类demoe2覆盖了特性A中的demo2 */
        echo 'Child->demo2';
    }
    /* 避免重复代码,A中抽象方法demo3实现暂时忽略 */
}
$obj = new ChildClass();
$obj ->demo1(); /* A->demo1,特性A的demo1覆盖了基类的demo1 */
$obj ->demo2(); /* Child->demo2,自身类的demo2覆盖了特性A中demo2 */

(7)优先级冲突解决

当两个特性的方法相同时,需要通过 “insteadof” 关键字定义谁优先,或通过 “as” 关键字修改方法名。先定义一个方法与 A 冲突的特性 D

trait D{
    function demo1(){
        echo 'C->demo1';
    }
}

普通类同时加载 AD

class ChildClass {//extends  ParentClass {
    //use A, D; /* 方法重名,且没有自定义优先级,则报错:PHP Fatal error:   Trait method demo1 has not been applied, because there are collisions with other trait methods*/
    //通过insteadof人为地定义优先级,解决冲突,或者通过as给其中一个方法取个别名
    use A, D {
        D::demo1 insteadof A;
        //D::demo1 AS dDemo1;
    }
    /* 避免重复代码,A中抽象方法实现暂时忽略 */
}

8)修改特性方法的访问控制级别。

在普通类中加载特性,也可通过 “as” 来修改其访问控制级别,如下示例代码:

class ChildClass {
    use C {
        demo5 as private;
    }
}

final 类

如果不希望一个类被继承,可以使用 “final” 来修饰。如果一个方法不想被子类覆盖,也可以这样声明。

结合 PHP 示例来看。

(1)final 修饰的函数不能被覆盖

class A{
    final function b(){
    }
}
class B extends A {
    function b(){//PHP Fatal error:  Cannot override final method A::b()
        echo 'B->b';
    }
}

(2)final 修饰的类不能被继承

final class A{
    function b(){
    }
}
class B extends A {// PHP Fatal error:  Class B may not inherit from final class (A)
}

类的属性不能定义为 final,只有类和类方法才能被定义为 final

匿名类

当我们想快速实例化一个对象,可以通过匿名类来实现。从 PHP 7 开始支持匿名类,可通过 new class 函数创建,不能有类名。

快速创建一个简单匿名对象:

$obj = new class {
    public function b($msg){
        return $msg;
    }
};
var_dump($obj->b('anonymous')); //输出'anonymous'
var_dump($obj); // object(class@anonymous)#1 (0) {}