PHP 中的特征与继承
前几天,我在进行代码审查时发现,一个开发人员使用trait为两个类提供了同一组实用程序方法。虽然这在功能方面没有任何问题,但我问开发人员为什么他们选择使用特征而不是继承。我们最终决定继承模型更适合这种情况,但我想我会在这里经历一些思考过程。
什么是特质?
如果您不知道,特征就像一个类,但您不会直接实例化它。特征是使用trait关键字定义的,否则在结构上很像一个类。
这个想法是,代码本质上是从特征复制到您想要在其中使用它的类中,并且该类的行为就像它一直都有该代码一样。例如,让我们采用一个简单的特征。
trait Hello { protected $name = 'Bob'; public function sayHello() { echo 'Hello ' . $this->name; } }
要在类中使用此特征,您可以将它与use关键字一起包含在类的结构中。
class Welcome { use Hello; }
现在,我们可以sayHello()从Hellotrait中调用该方法,就好像它是Welcome类的一部分一样。
$welcome = new Welcome(); $welcome->sayHello(); //打印“你好鲍勃”。
什么是PHP类继承?
虽然我在谈论如何使用特征,但(非常简短地)涵盖类继承是有意义的。在PHP中,您可以使用类来表示对象。例如,假设我们要表示一个正方形。我们需要存储高度和宽度,有一些添加这些属性的方法,也有获取面积的方法。
class Square { public $width; public $height; public function __construct($width, $height) { $this->width = $width; $this->height = $height; } public function getArea() { return $this->width * $this->height; } }
如果我们想表示一个三角形,那么我们可以创建另一个根本不连接到Square的类,或者我们可以创建一个抽象的Shape类并从中继承我们需要的东西。三角形具有与我们的形状相同的基本属性(宽度和高度),因此沿着继承路线走下去是有意义的。
abstract class Shape { public $width; public $height; public function __construct($width, $height) { $this->width = $width; $this->height = $height; } }
通过继承这个类,我们可以表示正方形和三角形,而无需复制一堆代码。我们使用extends关键字来做到这一点。
class Square extends Shape { public function getArea() { return $this->width * $this->height; } } class Triangle extends Shape { public function getArea() { return ($this->width * $this->height) / 2; } }
如何使用特征?
特性在PHP世界中使用非常普遍。它们主要包装诸如记录器和实用程序方法之类的东西,并用于注入此功能,而不必依赖复杂的继承模式。
例如,在DrupalJSONAPI模块中有一个称为ResourceIdentifierTrait的特征。模块使用它来添加常用方法,例如getId(),getTypeName(),以及getResourceType()使用它的任何类。这由EntityAccessDeniedHttpException异常类在模块内使用,该类已经继承了CacheableAccessDeniedHttpException类。这意味着我们可以允许EntityAccessDeniedHttpException类知道它被调用的上下文,而不必改变CacheableAccessDeniedHttpException类来知道被拒绝的资源的ID,这在Drupal的其他地方并不总是存在。
在我看来,特质用于解决PHP不支持多重继承的事实。回想一下Shape继承示例,我们可能还想表示形状的颜色。由于颜色可以以多种不同的方式存储,我们不想将其注入到shape对象中,因为这会污染Shape类所表示的内容,因为这会违反SOLID原则。我们也不能创建一个同时继承Shape和Color的Square类,因为这在PHP中是不允许的。
多重继承的经典解决方案是创建一个子继承类。例如,为了允许所有形状使用颜色,我们将Shape扩展为ShapeColour类,然后将其扩展为Square类。尽管这有效,但它确实在对象之间创建了许多奇怪的关系。
解决这个问题的方法是创建一个颜色特征。
trait ColorTrait { protected $colour; public function setColour($colour) { $this->colour = $colour; } public function getColor() { return $this->colour; } }
然后我们可以在Shape类中使用它来允许所有形状具有颜色。
abstract class Shape { use ColourTrait; public $width; public $height; public function __construct($width, $height) { $this->width = $width; $this->height = $height; } }
现在,当我们创建一个Square时,我们还可以同时设置Color。
$square = new Square(10, 10); $square->setColour('red'); echo $square->getColour();