抽象类和接口的区别

抽象类与接口相似,都是一种比较特殊的类。抽象类是一种特殊的类,而接口也是一种特殊的抽象类。它们通常配合面向对象的多态性一起使用。虽然声明和使用都比较容易,但它们的作用在理解上会困难一点。

抽象类

在OOP语言中,一个类可以有一个或多个子类,而每个类都有至少一个公有方法作为外部代码访问它的接口。而抽象方法就是为了方便继承而引入的。本节中先来介绍一下抽象类和抽象方法的声明,然后再说明其用途。在声明抽象类之前,我们先了解一下什么是抽象方法。抽象方法就是没有方法体的方法,所谓没有方法体是指在方法声明时没有花括号及其中的内容,而是在声明方法时直接在方法名后加上分号结束。另外在声明抽象方法时,还要使用关键字 abstract 来修饰。声明抽象方法的格式如下所示:

abstract function fun1(); //不能有花括号,就更不能有方法体中的内容了
abstract function fun2(); //直接在方法名的括号后面加上分号结束,还要使用 abstract 修饰

只要在声明类时有一个方法时抽象方法,那么这个类就是抽象类,抽象类也要使用 abstract 关键字来修饰。在抽象类中可以有不是抽象的成员方法和成员属性,但访问权限不能使用private关键字修饰为私有的。下面的例子在Person类中声明了两个抽象方法 say()eat() ,则 Person 类就是一个抽象类,需要使用 abstract 标识。代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
abstract class Person{
protected $name;
protected $country;

function __construct($name="",$country="china"){
$this ->name = $name;
$this ->country = $country;
}

abstract function say();
abstract function eat();

function run(){
echo "使用两条腿走路<br>";
}
}
?>

在上例中声明了一个抽象类 Person ,在这个类中定义了两个成员属性、一个构造方法和两个抽象方法,还有一个是非抽象的方法。抽象类就像是一个“半成品”的类,在抽象类中有没有被实现的抽象方法,所以抽象类是不能被实例化的,即创建不了对象,也就不能直接使用它。既然抽象类是一个“半成品”的类,那么使用抽象类有什么作用呢?使用抽象类就包含了继承关系,它是为它的子类定义公共接口,将它的操作(可能是部分,也可能是全部)交给子类去实现。就是将抽象类作为子类重载的模板使用,定义抽象类就相当于定义了一种规范,这种规范要求子类去遵守。当子类继承抽象类以后,就必须把抽象类中的抽象方法按照子类自己的需要去实现。子类必须把父类中的抽象方法全部都实现,否则子类中还存在抽象方法,所以还是抽象类,也不能实例化对象。在下例中声明了两个类,分别实现上例中声明的抽象类 Person 。代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
class ChineseMan extends Person{

function say(){
echo $this ->name."是".$this->country."人,讲汉语<br>";
}

function eat(){
echo $this ->name."使用筷子吃饭<br>";
}
}

class Americans extends Person{

function say(){
echo $this ->name."是".$this->country."人,讲英语<br>";
}

function eat(){
echo $this ->name."使用刀子和叉子吃饭<br>";
}
}

$chianeseMan = new ChineseMan("高洛峰","中国");
$americans = new Americans ("alex","美国");

$chineseMan ->say();
$chineseMan ->eat();

$americans ->say();
$americans ->eat();
?>

在上例中声明了两个类去继承抽象类 Person ,并将 Person 类中的抽象方法按各自的需求分别实现,这样两个子类就都可以创建对象了。抽象类 Person 就可以看成是一个模板,类中的抽象方法自己不去实现,只是规范了子类中必须要有父类中声明的抽象方法,而且要按照自己的特点实现抽象方法的内容。

接口

因为 PHP 只支持单继承,也就是说每个类只能继承一个父类。当声明的新类继承抽象类实现模板以后,它就不能再有其他父类了。为了解决这个问题, PHP 引入了接口。接口是一种特殊的抽象类,而抽象类又是一种特殊的类。如果抽象类中的所有方法都是抽象方法,我们就可以换另外一种声明方式,使用“接口”技术。接口中声明的方法必须都是抽象方法,另外不能再接口中声明变量,只能使用 const 关键字声明为常量的成员属性,而且接口中所有成员都必须有 public 的访问权限。类的声明是使用 class 关键字标识的,而接口的声明则是使用 interface 关键字标识的。声明接口的格式如下所示:

1
2
3
4
5
6
<?php
interface 接口名称{ //使用interface关键字声明接口
常量成员 //接口中的成员属性只能是常量,不能是变量
抽象方法 //接口中的所有方法必须是抽象方法,不能有非抽象的方法存在
}
?>

接口中的所有方法都要求是抽象方法,所以就不需要在方法前使用 abstract 关键字标识了。而且在接口中也不需要显式地使用 public 访问权限进行修饰,因为默认权限就是 public 的,也只能是公有的。另外接口和抽象类一样也不能实例化对象,它是一种更严格的规范,也需要通过子类来实现。但可以直接使用接口名称在接口外面去获取常量成员的值。一个接口的声明例子,代码如下所示:

1
2
3
4
5
6
7
<?php
interface one{
const CONSTANT = 'CONSTANT value';
function fun1();
function fun2();
}
?>

也可以使用 extends 关键字让一个接口去继承另一个接口,实现接口之间的扩展。在下面的例子中声明一个 Two 接口继承了上例中的 One 接口。代码如下所示:

1
2
3
4
5
6
<?php
interface Two extends one{
function fun3();
function fun4();
}
?>

如果需要使用接口中的成员,则需要通过子类去实现接口中的全部抽象方法,然后创建子类的对象去调用在子类中实现后的方法。但通过类去继承接口时需要使用 implements 关键字来实现,而并不是使用 extends 关键字完成。如果需要使用抽象类去实现接口中的部分方法,也需要使用 implements 关键字实现。在下面的例子中声明一个抽象类 Three 去实现 One 接口中的部分方法,但要想实例化对象,这个抽象类还需要有子类把它所有的抽象方法都实现才行。声明一个 Four 类去实现 One 接口中全部方法。代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
interface one{
const CONSTANT = 'CONSTANT value';
function fun1();
function fun2();
}

//声明一个抽象类去实现接口One中的第二个方法
abstract class Three implements One{ //只实现接口中的一个抽象方法
function fun2(){
//具体的实现内容由子类自己决定
}
}

//声明一个类实现接口One中的全部抽象方法
class Four implements One{
function fun1(){
//具体的实现内容由子类自己决定
}

function fun2(){
//具体的实现内容由子类自己决定
}
}

?>

PHP 是单继承的,一个类只能有一个父类,但是一个类可以实现多个接口。将要实现的多个接口之间使用逗号分隔开,而且在子类中药将所有接口中的抽象方法全部实现才可以创建对象。就相当于一个类要遵守多个规范,就像我们不仅要遵守国家的法律,如果是在学校,还需要遵守学校的校规一样。实现多个接口的格式如下所示:

1
2
3
class 类名 implements 接口一,接口二,……接口n{
实现所有接口中的抽象方法
}

实现多个接口是使用 implements 关键字,同时还可以使用 extends 关键字继承一个类。即在继承类的同时实现多个接口,但一定更要先使用extends继承一个类,再去使用 inplements 实现多个接口。使用格式如下所示:

1
2
3
class 类名 extends 父类名 implements 接口一,接口二,……接口n{
实现所有接口中的抽象方法
}

除了上述的一些应用外,还有很多地方可以使用使用接口,例如对于一些已经开发好的系统,在结构上进行较大的调整已经不太现实,这时可以通过自定义一些接口并追加相应的实现来完成功能结构的扩展。

区别

  • 对接口的使用方式是通过关键字 implements 来实现的,而对于抽象类的操作是使用类继承的关键字 extends 实现的,使用时要特别注意。
  • 接口没有数据成员,但是抽象类有数据成员,抽象类可以实现数据的封装。
  • 接口没有构造函数,抽象类可以有构造函数。
  • 接口中的方法都是 public 类型,而抽象类中的方法可以使用 privateprotectedpublic 来修饰。
  • 一个类可以同时实现多个接口,但是只能实现一个抽象类。

如何选择

  • 如果要创建一个模型,这个模型将由一些紧密相关的对象采用,就可以使用抽象类。如果要创建将由一些不相关对象采用的功能,就使用接口。
  • 如果必须从多个来源继承行为,就使用接口。
  • 如果知道所有类都会共享一个公共的行为实现,就使用抽象类,并在其中实现该行为。

Read More:

PHP的抽象类和接口 PHP的抽象类、接口的区别和选择 PHP高级——抽象类与接口的区别