类的扩展

开发者使用 OOP 的主要原因之一是它能够重用现有的代码,同时,还可以添加或覆盖功能。在 PHP 中,关键字 extends 用来建立类之间的父/子关系。

如何做...

1.在子类中,使用关键字 extends 来设置继承。在下面的例子中,Customer 类扩展了 Base 类。 Customer 的任何实例都将继承可见的方法和属性,在本例中为 $idgetId()setId()

class Base
{
  protected $id;
  public function getId()
  {
    return $this->id;
  }
  public function setId($id)
  {
    $this->id = $id;
  }
}

class Customer extends Base
{
  protected $name;
  public function getName()
  {
    return $this->name;
  }
  public function setName($name)
  {
    $this->name = $name;
  }
}

2.您可以强迫任何使用你的类的开发者通过标记为 abstract 的方法来定义一个方法。在这个例子中,Base 类将 validate() 方法定义为 abstract。之所以它必须是抽象的,是因为从父类 Base 类的角度无法准确地确定子类如何验证:

abstract class Base
{
  protected $id;
  public function getId()
  {
    return $this->id;
  }
  public function setId($id)
  {
    $this->id = $id;
  }
  public function validate();
}

如果一个类包含一个抽象方法,那么这个类本身必须声明为抽象的。

3.PHP 仅支持单线继承。 下面的示例显示了一个类 Member,它继承了 Customer 。而 Customer 则继承自 Base

class Base
{
  protected $id;
  public function getId()
  {
    return $this->id;
  }
  public function setId($id)
  {
    $this->id = $id;
  }
}

class Customer extends Base
{
  protected $name;
  public function getName()
  {
    return $this->name;
  }
  public function setName($name)
  {
    $this->name = $name;
  }
}

class Member extends Customer
{
  protected $membership;
  public function getMembership()
  {
    return $this->membership;
  }
  public function setMembership($memberId)
  {
    $this->membership = $memberId;
  }
}

4.为了满足类型提示,可以使用目标类的任何子类。在下面的代码片段中显示的 test() 函数,需要一个 Base 类的实例作为参数。继承线内的任何类都可以被接受为参数。传递给 test() 的其他任何东西都会抛出一个 TypeError

function test(Base $object)
{
  return $object->getId();
}

如何运行...

在第一个要点中,定义了一个 Base 类和一个 Customer 类。为了便于演示,将这两个类的定义放在一个文件中,即 chap_04_oop_extends.php ,并添加以下代码:

$customer = new Customer();
$customer->setId(100);
$customer->setName('Fred');
var_dump($customer);

请注意,$id 属性以及 getId()setId() 方法都是从父类 Base 类继承到子类 Customer 中的:

为了说明抽象方法的使用,想象一下,你希望给任何扩展 Base 的类添加某种验证能力。问题是,我们无法知道在继承的类中可能会验证什么。唯一可以确定的是,你必须有一个验证能力。

把前面解释中提到的同一个 Base 类,添加一个新的方法 validate() 。将该方法标记为抽象方法,并且不要定义任何代码。注意当子类 Customer 类扩展 Base 时,会发生什么。

如果你再把 Base 类标记为抽象类,但没有在子类中定义一个 validate() 方法,就会产生同样的错误。最后,继续在子类 Customer 中实现 validate() 方法:

class Customer extends Base
{
  protected $name;
  public function getName()
  {
    return $this->name;
  }
  public function setName($name)
  {
    $this->name = $name;
  }
  public function validate()
  {
    $valid = 0;
    $count = count(get_object_vars($this));
    if (!empty($this->id) &&is_int($this->id)) $valid++;
    if (!empty($this->name) 
    &&preg_match('/[a-z0-9 ]/i', $this->name)) $valid++;
    return ($valid == $count);
  }
}

然后你可以添加以下代码来测试结果:

$customer = new Customer();

$customer->setId(100);
$customer->setName('Fred');
echo "Customer [id]: {$customer->getName()}" .
     . "[{$customer->getId()}]\n";
echo ($customer->validate()) ? 'VALID' : 'NOT VALID';
$customer->setId('XXX');
$customer->setName('$%£&*()');
echo "Customer [id]: {$customer->getName()}"
  . "[{$customer->getId()}]\n";
echo ($customer->validate()) ? 'VALID' : 'NOT VALID';

这是输出:

为了展示单线继承,在前面步骤1所示的 BaseCustomer 的第一个例子中添加一个新的 Member 类:

{
  protected $membership;
  public function getMembership()
  {
    return $this->membership;
  }
  public function setMembership($memberId)
  {
    $this->membership = $memberId;
  }
}

创建一个 Member 的实例,注意,在下面的代码中,所有的属性和方法都可以从每个继承类中获得,即使不是直接继承的:

$member = new Member();
$member->setId(100);
$member->setName('Fred');
$member->setMembership('A299F322');
var_dump($member);

这是输出:

现在定义一个函数 test(),该函数将 Base 的实例作为参数:

function test(Base $object)
{
  return $object->getId();
}

请注意,BaseCustomerMember的实例都可以作为参数。

$base = new Base();
$base->setId(100);

$customer = new Customer();
$customer->setId(101);

$member = new Member();
$member->setId(102);

// 3个类都可以在 test() 中工作
echo test($base)     . PHP_EOL;
echo test($customer) . PHP_EOL;
echo test($member)   . PHP_EOL;

这是输出:

但是,如果你试图用一个不在继承线中的对象实例运行 test() ,就会抛出 TypeError

class Orphan
{
  protected $id;
  public function getId()
  {
    return $this->id;
  }
  public function setId($id)
  {
    $this->id = $id;
  }
}
try {
    $orphan = new Orphan();
    $orphan->setId(103);
    echo test($orphan) . PHP_EOL;
} catch (TypeError $e) {
    echo 'Does not work!' . PHP_EOL;
    echo $e->getMessage();
}

我们可以从下图中观察到这一点:

最后更新于