【转载】该用 Abstract Class 还是 Interface?

来源:互联网 发布:淘宝开网店的保证金 编辑:程序博客网 时间:2024/05/11 18:36

按三点答:

先答属性:抽象类可以有普通成员变量,接口不行;

然后答方法:抽象类有非抽象的方法和构造方法,并且修饰符可以是私有的,接口只能是抽象的方法,并且修饰符是public;

最后说一句只能继承一个抽象类,但能实现多个接口。

 

在编写程序的时候我常常陷入纠结,一个抽象对象,到底应该定义成 抽象类(Abstract Class) 还是 接口(Interface) 呢?二者具有很大的相似性,甚至可以相互替换,难以选择。在 Stackoverflow 上这个问题被问了很多次,各种编程语言的都有。而在 PHP 官网文档 Abstract Class 和 Interface 章节下面的评论里,人们也是争论不休。为了弄明白这个问题,必须仔细对比一下二者的区别和使用场景。

一、Abstract Class 与 Interface 的构造

抽象类 Abstract Class

  1. <?php
  2. abstract class A {
  3.     abstract public function method1();
  4.     abstract public function method2();
  5.     public function method3() {
  6.         //... code ...
  7.     }
  8. }
  9. ?>
复制代码

接口 Interface
  1. <?php
  2. interface B {
  3.     public function method4();
  4.     public function method5();
  5. }
  6. ?>
复制代码

可以看到,Abstract Class 中可以有抽象函数(method1,method2),也可以有具体的函数实现(method3),而 Interface 中只能定义函数而不能有实现(method4,method5)。显然,如果一个抽象类中的函数全部是抽象函数,那么这个抽象类就退化成了接口。不过要注意的是

PHP 和 Java 一样,一个 Class 只能继承一个 Abstract Class,但可以实现多个 Interface。这即是所谓的单一继承体系,也就是子类别只能继承一个父类别;一个父类别则可以被多个子类别所继承。据说在 Python 中是可以多重继承的。
在 Abstract Class 中可以声明属性成员变量(attribute),而 Interface 不可以。

二、举例

下面的例子可以帮助我们从另一个层面:Abstract Class 和 Interface 所反映出的设计理念,来分析一下二者的本质区别。

假设我们要定义一个关于“门”的 Class,门有“开”和“关”两个动作。此时我们既可以用 Abstract Class 也可以用 Interface 来描述这个抽象概念——

抽象类 Abstract Class
  1. <?php
  2. abstract class Door {
  3.     abstract public function open();
  4.     abstract public function close();
  5. }
  6. ?>
复制代码

接口 Interface
  1. <?php
  2. interface Door {
  3.     public function open();
  4.     public function close();
  5. }
  6. ?>
复制代码

在 Abstract Class 中并没有实现开门和关门方法,因为有的门可以用钥匙打开,有的门用密码打开,还有的门可以用指纹打开,这些具体方法就交给子类继承去实现。在这样看上去 Abstract Class 和 Interface 二者的使用没有太大差别。

现在如果定义一种具有“报警”功能的门,Abstract Class 和 Interface 描述类似——

抽象类 Abstract Class
  1. <?php
  2. abstract class Door {
  3.     abstract public function open();
  4.     abstract public function close();
  5.     abstract public function alarm();
  6. }
  7. class AlarmDoor extends Door {
  8.     public function open() {
  9.         //... 
  10.     }
  11.     public function close() {
  12.         //... 
  13.     }
  14.     public function alarm() {
  15.         //... 
  16.     }
  17. }
  18. ?>
复制代码

接口 Interface
  1. <?php
  2. interface Door
  3. {
  4.     public function open();
  5.     public function close();
  6.     public function alerm();
  7. }
  8. class AlarmDoor implements Door {
  9.     public function open() {
  10.         //... 
  11.     }
  12.     public function close() {
  13.         //... 
  14.     }
  15.     public function alarm() {
  16.         //... 
  17.     }
  18. }
  19. ?>
复制代码

很明显上面的做法是不对的。它在定义中把 Door 本身固有的开门、关门的行为方法和另外一个概念“报警器”的行为方法混在了一起。有的门可以报警,而有的门可能没有报警功能;而会报警的门也可能多种多样(钥匙开门,刷卡开门等等)。我们必须将报警行为单独定义到另一个对象中,那么就有 3 种方式:

  • 门和报警器都用 abstract class 方式定义;
  • 门和报警器都用 interface 方式定义;
  • 一个使用 abstract class 方式定义,另一个使用 interface 方式定义。

由于 PHP 中子类只可继承自一个 Abstract Class,而且 Abstract Class 不支持多重继承(abstract class cannot extend abstract class),所以第一种方式显然不行。而第二种方式则没有能够正确的揭示我们的设计意图,也就是没有反映出 AlarmDoor 在概念本质上是 Door,同时它有具有报警功能。所以对于 Door 这个概念,我们应该采取 Abstract Class 方式来定义,AlarmDoor 继承自 Door,而报警概念通过 Interface 方式定义。
  1. <?php
  2. abstract class Door {
  3.     abstract function open();
  4.     abstract function close();
  5. }
  6. interface Alarm {
  7.     function alarm();
  8. }
  9. class AlarmDoor extends Door implements Alarm {
  10.     public function open() {
  11.         //... 
  12.     }
  13.     public function close() {
  14.         //... 
  15.     }
  16.     public function alarm() {
  17.         //... 
  18.     }
  19. }
  20. ?>
复制代码

这样的实现基本上正确地反映了我们的设计意图。接口是可以多重继承的,子类也可以实现多个接口。这样就使得我们可以继续扩展门的功能,比如给门再装个摄像头……

三、结论

当我们使用 Abstract Class 的时候,我们应该定义一类对象的属性,即描述它 是什么。而 Interface 则类似于插件,侧重于提供附加的能力,约定它 能做什么
原创粉丝点击