在PHP面向对象编程中,抽象类(Abstract Class)和接口(Interface)都是为了提供一种规范化的途径,强制子类去实现特定的方法,以达到某种设计要求或契约。以下是它们的主要区别:
抽象类(Abstract Class):
- 定义:抽象类通过使用
abstract关键字来声明,它可以包含抽象方法(使用abstract声明并且没有具体实现的方法)和普通方法(有具体实现的方法)以及属性(成员变量)。 - 特性:
-
抽象类可以有构造函数,并且可以有非抽象(具体实现)的方法和属性。
-
子类必须继承抽象类,并实现其所有抽象方法,否则子类也需要声明为抽象类。
-
抽象类自身不能实例化,只能通过其非抽象子类实例化。
-
可以使用public,protect,private
abstract class test1{ abstract private function test()//不能这样用 private function test1(){ //可以这样用 echo "123"; } } -
抽象方法只有方法的声明,而没有方法体
-
抽象类可以包含抽象方法和普通方法
-
抽象类可以像具体类一样调用内部成员
abstract class Onetwo {
private static $a = 1;
public function qwer() {
return self::$a;
}
}
class Wokao extends Onetwo {
public function __construct() {
$c = parent::qwer();
echo $c; // 为了演示目的,显示结果
}
}
$wokao = new Wokao(); // 实例化 Wokao 类
解释:
-
抽象类
Onetwo:抽象类Onetwo包含一个私有静态变量$a,初始值为 1,以及一个公共方法qwer()。private static $a = 1;:声明一个私有静态变量$a,只能在Onetwo类内部访问。其值设为 1。public function qwer() { return self::$a; }:定义一个公共方法qwer(),返回静态变量$a的值,使用self关键字,该关键字在此处指代当前类(即Onetwo)。此方法可以从Onetwo类的任何实例或其子类中调用。
-
具体类
Wokao:类Wokao继承自抽象类Onetwo。在__construct()方法中,我们调用父类的qwer()方法并将返回值赋给局部变量$c。接着,为了演示目的,我们使用echo输出$c的值。public function __construct() { ... }:构造方法在创建Wokao类实例时被调用。$c = parent::qwer();:使用parent关键字调用父类(Onetwo)中定义的qwer()方法。返回值被赋给局部变量$c。echo $c;:显示$c的值,以示例说明父类的qwer()方法成功被调用,其返回值已存储在Wokao类实例的$c变量中。
-
实例化
Wokao:最后,我们创建Wokao类的一个实例 ($wokao = new Wokao();),这会触发构造方法并执行其中的代码。
运行此修正后的代码时,将会输出 "1",这是 Onetwo 类中私有静态变量 $a 的值,通过 qwer() 方法访问,并存储在 Wokao 类实例的 $c 变量中。
接口(Interface):
- 定义:接口使用
interface关键字来声明,它只包含常量和抽象方法(PHP 7.4及以后版本允许定义私有方法,但仍然不能有属性和具体实现)。 - 特性:
- 接口中所有的方法默认为
public类型,并且都不能有任何实现(在PHP 8之前)。 - 接口不能包含属性(除了常量,默认为
public和static)和构造函数。 - 类可以通过
implements关键字来实现一个或多个接口,并且必须实现接口中声明的所有方法。 - 接口可以继承其他接口,形成一个方法集合的扩展。
- 接口中所有的方法默认为
总结差异:
-
抽象类侧重于定义一个类的基本结构,允许包含部分实现,适用于类之间的层次结构关系较为紧密的情况,可作为模板类供子类扩展。
-
接口更强调纯粹的行为规范,是对类功能的一种约定,强制实现接口的类必须拥有某些公共方法,而不关心这些方法的具体实现。接口通常用于解耦组件间的依赖关系,使得不同的类能够共享同一套接口标准,实现多态。
-
如果子类继承抽象类时,子类中无抽象类方法,则需要将子类声明为抽象类,而子类继承接口时必须包含接口中的方法
abstract class lover{ public static $name; abstract public function eat(){ echo "eeat; } } //将eat也变成抽象类 abstract class eat extend lover{ //不必实现上述方法 function ear(){ 126; } } -
接口中只能为public,而抽象类中可以为public,protected和private
所以,在设计时选择抽象类还是接口,往往取决于你希望实现的设计模式和代码组织结构的要求。如果关注的是类的共同结构和部分共性实现,则使用抽象类;如果关注的是统一的对外行为规范,则更适合使用接口。
抽象类的例子及错误实例:
// 正确实例 - 定义一个抽象类及其抽象方法
abstract class Animal {
public $name;
abstract protected function makeSound(); // 抽象方法
public function __construct($name) {
$this->name = $name;
}
public function introduce() {
echo "This is an animal named {$this->name} and it makes this sound: ";
$this->makeSound();
}
}
// 继承抽象类并实现抽象方法
class Dog extends Animal {
protected function makeSound() {
echo "Woof!";
}
}
$dog = new Dog("Buddy");
$dog->introduce(); // 输出: This is an animal named Buddy and it makes this sound: Woof!
// 错误实例 - 忘记在子类中实现抽象方法
abstract class Animal {...}
class Cat extends Animal { // 编译错误,因为Cat类没有实现Animal中的抽象方法makeSound()
}
接口的例子:
// 定义接口
interface CanFly {
public function fly();
}
interface CanSwim {
public function swim();
}
// 创建一个实现了两个接口的类
class Duck implements CanFly, CanSwim {
public function fly() {
echo "The duck is flying.";
}
public function swim() {
echo "The duck is swimming.";
}
}
$duck = new Duck();
$duck->fly(); // 输出: The duck is flying.
$duck->swim(); // 输出: The duck is swimming.
// 错误实例 - 忘记在类中实现接口方法
interface CanJump {
public function jump();
}
class Elephant implements CanJump { // 编译错误,因为Elephant类没有实现CanJump接口的jump()方法
}
上述例子中,抽象类Animal定义了一个通用的结构并包含一个抽象方法makeSound(),Dog类继承Animal并实现了这个抽象方法。而接口CanFly和CanSwim分别定义了飞和游泳的行为规范,Duck类通过实现这两个接口确保了它具备这两种能力。在错误实例中,未实现抽象方法或接口方法的类会导致编译错误。