在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类通过实现这两个接口确保了它具备这两种能力。在错误实例中,未实现抽象方法或接口方法的类会导致编译错误。