php接口类与抽象类的区别

php接口类与抽象类的区别

接口和抽象类的定义

什么是接口类

接口类相当于一个类模板,让大家都遵守这个模板去写,这样方法的名字就能统一,而不是各造一套,所以,接口是规范,类是具体的实现。

  • 接口类只能定义公有(public)方法,并且只有方法名和参数名,不能有方法体;
  • 接口类不能有属性;
  • 接口类可以有类常量但不能被继承它的类中的同名常量覆盖(会报错);
  • 接口类可以有构造函数(因为其本质也是一个公有方法),但不能有方法体(相当于强制继承它的子类必须要写构造函数);
  • 一个类可同时继承(其实对于接口,应该叫实现,即implement)多个接口类;
  • 接口类不能被实例化;
  • 子类必须实现接口类的所有方法(即定义它的方法体,有一个空的花括号就算,并不要求里面一定要有语句,毕竟什么都不做也是一种实现)!

接口类用关键字interface来定义,除此之外,它长的像一个类,只不过方法只有名称和参数,没有方法体,事实上它就是一个特殊的类

/**
 * 定义一个接口类,通常用Interface结尾,表示这是一个接口
 * Interface PersonInterface
 */
interface PersonInterface
{
    public function sayHello();
    public function walk();
}

什么是抽象类

抽象类也是一个类模板,只不过它比接口类更强大(除了不能被多继承外),抽象类与接口类的本质区别就是一个类只能继承一个抽象类,而一个类可以同时继承(实现)多个接口

之所以说抽象类比接口更强大,是因为抽象类还有其它特点,这些特点相当于是对接口类的扩展,比如:

  • 接口类只能定义公有(public)方法,但抽象类还能定义保护(protected)和私有(private)方法;
  • 接口类定义的方法不能有方法体,而抽象类可以(当然抽象方法不能有方法体);
  • 构造函数定义为非抽象方法时,可以有方法体(当然也可以把构造方法定义为抽象方法,只不过这样就不能有函数体);
  • 接口类不能有属性,但抽象类可以有;
  • 接口类的常量无法被子类覆盖,但抽象类可以;
  • (共同点)接口类不能被实例化,抽象类也不可以;
  • (共同点)子类必须完全实现所有抽象类方法,接口类也一样;
  • (共同点)抽象类可以相互继承,接口类也可以相互继承(注意接口之间相互继承用extend,而不是implement)!

抽象类与普通类的写法区别就是在class前面多加一个abstract关键字,理论上一个抽象类至少需要有一个抽象方法才叫抽象类,但实际上,即使你一个抽象方法都没有,还是可以把它定义为抽象类(不会报错php 7.3.23),但这样的抽象类没有任何意义。

但是反过来,如果一个类里有一个抽象方法(在访问控制关键字前加abstract关键字),则这个类必须要声明为抽象类!

抽象类举例:

/**
 * 定义一个抽象类,通常用Abstract开头,表示这是个抽象类(只是规范而不是规定)
 * (为什么Interface是放在结尾而Abstract放在开头?因为从英文上来说,就该这么放,反过来就不通顺)
 * Class AbstractPerson
 */
abstract class AbstractPerson
{
    abstract public function sayHello();
    abstract public function walk();
}

抽象类与接口类的区别总结

  • 抽象类大概可以认为是更宽松的接口类,接口类和抽象类都是为了定义一个类模板,让继承它的类都按这个模板写;
  • 比如抽象类如果只定义抽象方法(无方法体),那么跟接口基本没什么区别(除了一个类可以实现多个接口外),但关键是抽象类还可以定义非抽象方法(可以有方法体)、属性;
  • 既然抽象类可以定义非抽象方法体,那就代表它可能需要构造函数初始化一些东西,没错,抽象类可以有构造函数;
  • 接口类定义的方法只能是public的,而抽象类的方法可以是public/protected/private(你没看错,是可以private,只不过private的方法只能是非抽象方法,而且只能抽象类内部调用);
  • 继承(实现)一个接口,方法的访问属性可以是public/protected/private中的任意一个,但继承一个抽象类,方法的访问属性要与抽象类中对应的方法相同或更宽松(比如抽象类的某个方法是protected,则子类的该方法必须是protected或public,而不是能是private);
  • 虽然接口和抽象类都可以定义类常量(const定义),但接口的类常量不会被子类或子接口的同名常量覆盖(会报错),而抽象类的类常量是可以被子类覆盖的;
  • 一个类可以同时继承多个接口类,但只能继承一个抽象类;
  • 一个类继承接口,其实不叫继承,而是叫实现一个接口,所以关键字用implement,而继承一个抽象类,就是继承,所以关键字用extend;
  • 接口类和抽象类都不能被实例化,因为方法都没方法体,就算能实例化,你实例化后调用的函数都是没有方法体的(或者至少有一个没有方法体,如果抽象类只有一个抽象方法的话)。
  • 一般写法是抽象类继承接口,而普通类再继承这个继承接口的抽象类,当然普通类也可以直接继承接口;
  • 当实现多个接口时,这多个接口之间可以有同名方法,但必须参数个数,参数名也相同,否则会报错;
  • 主要区别:抽象类只能单个继承,而接口则可以多个继承,否则抽象类完全可以代替接口类(所有方法都写抽象方法即可)!

一些实例代码

不能用抽象类代替接口类

当抽象类只有抽象方法的时候,基本上就相当于是接口了,因为子类也必须全部实现抽象类的所有抽象方法,所以,看上去抽象类是可以代替接口类的

/**
 * 定义一个接口类,通常用Interface结尾,表示这是一个接口
 * Interface iPerson
 */
interface PersonInterface
{
    public function sayHello();
    public function walk();
}

/**
 * 定义一个抽象类,通常用Abstract开头,表示这是个抽象类(只是规范而不是规定)
 * (为什么Interface是放在结尾而Abstract放在开头?因为从英文上来说,就该这么放,反过来就不通顺)
 * Class AbstractPerson
 */
abstract class AbstractPerson
{
    abstract public function sayHello();
    abstract public function walk();
}

但如果是这样,为什么又要设置出接口类来呢?一方面可能是为了跟其它语言统一,另一方面,就算抽象类只写抽象方法,还是跟接口有区别的,就是抽象类不能被同时继承多个,而接口类可以,这也是抽象类与接口的主要区别,所以最好还是不要用抽象类代替接口

抽象类定义非抽象方法

抽象类定义非抽象方法,一般是用于公共方法,即当有一个需求:所有继承该类的子类都需要调用一个公共的方法时,就可以把这个方法放在它们共同继承的抽象类里,否则,你就要特地用一个公共的类继承抽象类,把公共方法放在这个公共的类里,再让多个类继承这个公共的类。

/**
 * 定义一个抽象类,通常用Abstract开头,表示这是个抽象类
 * (为什么Interface是放在结尾而Abstract放在开头?因为从英文上来说,就该这么放,反过来就是语法错误)
 * Class Person
 */
abstract class AbstractPerson
{
    abstract public function sayHello();
    abstract public function walk();

    //抽象类定义非抽象方法,一般是用于公共方法,
    //即当继承它的多个类都有访问同一个公共方法的需求时,
    //则这个公共方法就可以写在抽象类里面,并且是有方法体的。
    public function common(){
        echo 'This is a AbstractPerson class';
    }
}

抽象类定义属性

抽象类中可以定义属性,而接口类不可以。

/**
 * 定义一个抽象类,通常用Abstract开头,表示这是个抽象类
 * (为什么Interface是放在结尾而Abstract放在开头?因为从英文上来说,就该这么放,反过来就不通顺)
 * Class Person
 */
abstract class AbstractPerson
{
    /**
     * 抽象类中可以定义属性
     */
    public $gender;
    public $eyeColor;
    public $skinColor;

    abstract public function sayHello();
    abstract public function walk();
}

抽象类定义构造函数

其实构造函数也属于非抽象方法,所以抽象类的构造函数可以有函数体(相比之下,接口类可以定义构造函数,但不能有函数体)。

从另一个角度来说,因为抽象类可能有非抽象方法和属性,而属性就有可能需要初始化,所以这也是它可以有构造函数的另一个原因。

/**
 * 定义一个抽象类,通常用Abstract开头,表示这是个抽象类
 * (为什么Interface是放在结尾而Abstract放在开头?因为从英文上来说,就该这么放,反过来就不通顺)
 * Class Person
 */
abstract class AbstractPerson
{
    public $gender;
    public $eyeColor;
    public $skinColor;

    abstract public function sayHello();
    abstract public function walk();

    /**
     * 抽像类可以定义有函数数的构造函数
     *
     * @param $gender
     * @param $eyeColor
     * @param $skinColor
     */
    public function __construct ($gender, $eyeColor, $skinColor){
        $this->gender = $gender;
        $this->eyeColor = $eyeColor;
        $this->skinColor = $skinColor;
    }

    public function common(){
        echo 'This is a AbstractPerson class';
    }
}

抽象类可以定义protected/private方法

与接口类只能定义public方法不同,抽象类还可以定义protected和private方法,当然抽象方法是不能定义为private的,不然,一方面,根据抽象方法的规则,要求子类继承并实现抽象类中的抽象方法,另一方面,private又不允许它被继承,就冲突了!

/**
 * 定义一个抽象类,通常用Abstract开头,表示这是个抽象类
 * (为什么Interface是放在结尾而Abstract放在开头?因为从英文上来说,就该这么放,反过来就不通顺)
 * Class Person
 */
abstract class AbstractPerson
{
    //抽象类可以定义protected方法
    abstract protected function sayHello();
    abstract public function walk();
    //抽象类也可以定义private方法,当然private方法不能是抽象方法
    private function priFun(){
        echo 'This is a private method';
    }
    public function pubFun(){
        //private方法只能被内部调用
        $this->priFun();
    }
}

继承抽象类时的访问属性

继承抽象类时,子类方法的访问属性要与抽象类中对应的抽象方法的访问属性相同或更宽松(比如抽象类的某个方法是protected,则子类的该方法必须是protected或public,而不是能是private),否则会报错。

本例的报错信息:PHP Fatal error: Access level to Person::sayHello() must be protected (as in class AbstractPerson) or weaker

/**
 * 定义一个抽象类,通常用Abstract开头,表示这是个抽象类
 * (为什么Interface是放在结尾而Abstract放在开头?因为从英文上来说,就该这么放,反过来就不通顺)
 * Class Person
 */
abstract class AbstractPerson
{
    abstract protected function sayHello();
    abstract public function walk();

}
//继承抽象类
class Person extends AbstractPerson{
    //sayHello()方法在抽象类中是protected,所以在这里把它
    //设置为private会报错,正确的是设置为protected或public
    private function sayHello(){
        echo 'Hello';
    }
    public function walk(){
        echo 'Walk';
    }
}

接口类常量不可被子类同名常量覆盖

接口类的常量不可被子类同名常量覆盖,也就是如果一个常量名被接口类定义了,那子类就不能定义与接口类这个常量名相同的常量了,否则报错。

本例报错:PHP Fatal error: Cannot inherit previously-inherited or override constant CONST_CLASS_NAME from interface PersonInterface

/**
 * 定义一个接口类,通常用Interface结尾,表示这是一个接口
 * Interface iPerson
 */
interface PersonInterface
{
    const CONST_CLASS_NAME = 'PersonInterface';
    public function sayHello();
    public function walk();
}

//implement(实现)一个接口
class Person implements PersonInterface{
    //接口类中有同名常量,所以这里不能再定义,所以本例是会报错的
    const CONST_CLASS_NAME = 'Person';
    public function sayHello (){
        echo 'Hello';
    }
    public function walk (){
        echo 'Walk';
    }
}

同时继承多个接口

接口与抽象类最大的区别,就是接口可以被继承多个,如果只继承一个,抽象类完全可以当接口用(所有方法都写抽象方法即可)

/**
 * 陆地类
 * Interface OnEarth
 */
interface OnEarth{
    public function earthMove();
}

/**
 * 水中类
 * Interface OnWater
 */
interface OnWater{
    public function waterMove();
}

/**
 * 近距离攻击
 * Interface NearAttack
 */
interface NearAttack{
    public function nearAttack();
}

/**
 * 远距离攻击
 * Interface FarAttack
 */
interface FarAttack{
    public function farAttack();
}

/**
 * 猎犬类(属于陆地近距离攻击,所以要实现OnEarth, NearAttack两个接口)
 * Class Tyke
 */
class Tyke implements OnEarth, NearAttack
{
    public function earthMove(){
        echo 'Move on earch!';
    }

    public function nearAttack(){
        echo 'near attack!';
    }
}

实现多个有同名方法的接口

这多个接口可以有同名方法,但必须同名,同参数名,同参数个数。

抽象类相互继承

抽象类可以继承抽象类,如果一个类A继承自一个抽象类B,并且抽象类B又继承自另一个抽象类C,则类A必须实现抽象类B和抽象类C里所有的方法。

/**
 * 抽象类1
 * Class AbstractPerson
 */
abstract class AbstractPerson
{
    abstract protected function sayHello();
    abstract public function walk();

}

/**
 * 抽象类2,继承抽象类1,相当于扩展抽象类1了
 * Class Person
 */
abstract class AbstractPerson2 extends AbstractPerson{
    abstract public function run();
}

/**
 * 继承抽象类2,必须实现抽象类2和抽象类1的
 * 所有抽象方法,因为抽象类2继承自抽象类1
 * Class Person2
 */
class Person2 extends AbstractPerson2{
    protected function sayHello (){
        echo 'Hello';
    }
    public function walk(){
        echo 'Walk';
    }
    public function run(){
        echo 'Run';
    }
}

接口类相互继承

interface a
{
    public function funFromInterfaceA();
}

//接口之间继承也是用extends关键字
interface b extends a
{
    public function funFromInterfaceB();
}

//实现接口b时,要把b和a两个接口的所有方法都实现(这点跟抽象类一样)
class c implements b
{
    public function funFromInterfaceA(){
        echo 'This is funFromInterfaceA!';
    }

    public function funFromInterfaceB(){
        echo 'This is funFromInterfaceB!';
    }
}

接口与抽象类都不能被实例化

以下代码实例化接口,会报错:PHP Fatal error: Uncaught Error: Cannot instantiate interface PersonInterface

interface PersonInterface
{
    public function sayHello();
    public function walk();
}
//实例化接口会报错
new PersonInterface;

以下代码实例化抽象类,会报错:PHP Fatal error: Uncaught Error: Cannot instantiate abstract class AbstractPerson

abstract class AbstractPerson
{
    abstract protected function sayHello();
    abstract public function walk();

}

new AbstractPerson;

本文是我在官网文档及网上文章的基础上加入了自己的理解总结出来的,如有错误请评论指出,感谢!

打赏
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x

扫码在手机查看
iPhone请用自带相机扫
安卓用UC/QQ浏览器扫

php接口类与抽象类的区别