通过更好的组织和代码重用来最大限度地减少代码重复是面向对象编程的一个重要目标。但是在PHP中,由于它使用的单一继承模型的局限性,有时可能会很困难;您可能希望在多个类中使用一些方法,但它们可能不太适合继承层次结构。 像C++和Python这样的语言允许我们从多个类继承,这在一定程度上解决了这个问题,而Ruby中的mixins允许我们在不使用继承的情况下混合一个或多个类的功能。但是多重继承存在诸如 Diamond 问题之类的问题,并且 mixins 可能是一个复杂的机制。 在本文中,我将讨论 traits,这是 PHP 5.4 中引入的一项新功能,用于克服此类问题。特征的概念本身对编程来说并不是什么新鲜事,它用于其他语言,如 Scala 和 Perl。它们允许我们在不同类层次结构中的独立类之间水平重用代码。
特质是什么样子的
特征类似于抽象类,它不能单独实例化(尽管更常见的是将其与接口进行比较)。PHP 文档定义特征如下:
Traits 是一种在单继承语言(如 PHP)中重用代码的机制。Trait 旨在通过使开发人员能够在位于不同类层次结构中的多个独立类中自由重用方法集来减少单个继承的一些限制。
让我们考虑这个例子:如果两个类都需要一些通用功能,例如使它们都成为单例,那将是一个问题。由于 PHP 不支持多重继承,因此每个类都必须实现必要的代码来支持单例模式,否则将存在没有意义的继承层次结构。特征为此类问题提供了解决方案。 该特征具有使用静态方法的单例模式的直接实现,该方法使用此特征(如果尚未创建)创建类的对象并返回它。 让我们尝试使用 方法创建这些类的对象。 我们可以看到 是 的对象,也是 的对象,但两者现在都表现为单例。from 方法已水平注入到使用它的类中。 特征不会对类施加任何其他语义。在某种程度上,你可以把它看作是一种编译器辅助的复制和粘贴机制,其中特征的方法被复制到组合类中。 如果我们只是从具有私有属性的父级进行子类化,则该属性不会显示在 的转储中。然而,有了特征,它就在那里!
<?php class DbReader extends Mysqli { } class FileReader extends SplFileObject { }
<?php trait Singleton { private static $instance; public static function getInstance() { if (!(self::$instance instanceof self)) { self::$instance = new self; } return self::$instance; } } class DbReader extends ArrayObject { use Singleton; } class FileReader { use Singleton; }
Singleton
getInstance()
getInstance()
<?php $a = DbReader::getInstance(); $b = FileReader::getInstance(); var_dump($a); //object(DbReader) var_dump($b); //object(FileReader)
$a
DbReader
$b
FileReader
Singleton
DbReader
$instance
ReflectionClass::export()
Class [ class FileReader ] { @@ /home/shameer/workplace/php54/index.php 19-22 - Constants [0] { } - Static properties [1] { Property [ private static $_instance ] } - Static methods [1] { Method [ static public method instance ] { @@ /home/shameer/workplace/php54/index.php 6 - 11 } } - Properties [0] { } - Methods [0] { } }
多种特征
到目前为止,我们只对一个类使用了一个特征,但在某些情况下,我们可能需要合并多个特征的功能。 在这里,我们有两个特征,并且.特质只能说“你好”,特质可以说“世界”。在我们应用的类中,对象将具有来自两个特征的方法,并且能够说“Hello World”。
<?php trait Hello { function sayHello() { echo "Hello"; } } trait World { function sayWorld() { echo "World"; } } class MyWorld { use Hello, World; } $world = new MyWorld(); echo $world->sayHello() . " " . $world->sayWorld(); //Hello World
由性状组成的性状
随着应用程序的发展,我们很可能会拥有一组跨不同类使用的特征。PHP 5.4 允许我们拥有由其他特征组成的特征,这样我们就可以在所有这些类中只包含一个特征而不是多个特征。这让我们重写前面的示例,如下所示: 在这里,我们使用 Hello 和 World 特征创建了 trait,并将其包含在 中。由于该特征具有来自其他两个特征的方法,因此就好像我们自己将这两个特征包含在类中一样。
<?php trait HelloWorld { use Hello, World; } class MyWorld { use HelloWorld; } $world = new MyWorld(); echo $world->sayHello() . " " . $world->sayWorld(); //Hello World
HelloWorld
MyWorld
HelloWorld
优先顺序
正如我已经提到的,特征的工作方式就好像它们的方法被复制并粘贴到使用它们的类中,并且它们完全被扁平化到类的定义中。在不同的特征或类本身中可能存在具有相同名称的方法。您可能想知道在子类的对象中可以使用哪一个。 优先级顺序为:
- trait 的方法重写从父类继承的方法
- 当前类中定义的方法会覆盖特征中的方法
在下面的例子中很清楚这一点:我们有一个派生自 的类,两个类都有一个名为但具有不同实现的方法。此外,我们已将该特征包含在类中。 我们有两种方法,其中前一种调用存在于两个类和特征中。但是在输出中,我们可以看到子类中的那个被调用了。如果我们需要从父类引用方法,我们可以使用 parent 关键字来实现,如方法中所示。
<?php trait Hello { function sayHello() { return "Hello"; } function sayWorld() { return "Trait World"; } function sayHelloWorld() { echo $this->sayHello() . " " . $this->sayWorld(); } function sayBaseWorld() { echo $this->sayHello() . " " . parent::sayWorld(); } } class Base { function sayWorld(){ return "Base World"; } } class HelloWorld extends Base { use Hello; function sayWorld() { return "World"; } } $h = new HelloWorld(); $h->sayHelloWorld(); // Hello World $h->sayBaseWorld(); // Hello Base World
HelloWorld
Base
sayWorld()
Hello
HelloWorld
sayHelloWorld()
sayBaseWorld()
sayWorld()
sayBaseWorld()
冲突解决和锯齿
使用多个特征时,可能会出现不同特征使用相同方法名称的情况。例如,如果您尝试运行以下代码,则由于方法名称冲突,PHP 将给出致命错误: 此类特征冲突不会自动为您解决。相反,您必须使用关键字 来选择应在组合类中使用哪种方法。 在这里,我们选择在作曲类中使用特质的方法,以便该类将播放音乐,而不是游戏。 在上面的示例中,从两个特征中选择了一种方法而不是另一种方法。在某些情况下,您可能希望同时保留它们,但仍要避免冲突。可以为特征中的方法引入新名称作为别名。别名不会重命名方法,但会提供可调用该方法的备用名称。别名是使用关键字 创建的。 现在类的任何对象都会有一个方法,它与 相同。这里应该注意的是,即使在别名之后,我们也已经明确解决了任何冲突。
<?php trait Game { function play() { echo "Playing a game"; } } trait Music { function play() { echo "Playing music"; } } class Player { use Game, Music; } $player = new Player(); $player->play();
insteadof
<?php class Player { use Game, Music { Music::play insteadof Game; } } $player = new Player(); $player->play(); //Playing music
<?php
class Player
{
use Game, Music {
Game::play as gamePlay;
Music::play insteadof Game;
}
}
$player = new Player();
$player->play(); //Playing music
$player->gamePlay(); //Playing a game
Player
gamePlay()
Game::play()
反射
Reflection API 是 PHP 的强大功能之一,用于分析接口、类和方法的内部结构并对其进行逆向工程。由于我们谈论的是特征,您可能有兴趣了解反射 API 对特征的支持。在 PHP 5.4 中,添加了四种方法来获取有关类中特征的信息。 我们可以用来获取类中使用的所有特征的数组。该方法将仅返回该类中的特征名称数组。 有助于检查某事是否是特征。 在上一节中,我们讨论了特征的别名,以避免由于同名特征而导致的冲突。 将返回映射到其原始名称的特征别名数组。ReflectionClass
ReflectionClass::getTraits()
ReflectionClass::getTraitNames()
ReflectionClass::isTrait()
ReflectionClass::getTraitAliases()
其他特性
除了上面提到的之外,还有其他功能使特征更有趣。我们知道,在经典继承中,子类无法访问类的私有属性。特征可以访问组合类的私有属性或方法,反之亦然!下面是一个示例:当特征完全扁平化为由它们组成的类时,特征的任何属性或方法都将成为该类的一部分,我们像访问任何其他类属性或方法一样访问它们。 我们甚至可以在特征中使用抽象方法来强制组合类实现这些方法。例如:这里我们有一个带有抽象方法的特征。它要求所有使用此特征的类来实现该方法。否则,PHP 会给出一个错误,说有一个尚未实现的抽象方法。 与 Scala 中的 traits 不同,PHP 中的 tras 可以有一个构造函数,但它必须声明为公共的(如果是私有的或受保护的,则会抛出错误)。无论如何,在特征中使用构造函数时要小心,因为它可能会导致组合类中出现意外冲突。<?php trait Message { function alert() { echo $this->message; } } class Messenger { use Message; private $message = "This is a message"; } $messenger = new Messenger; $messenger->alert(); //This is a message
<?php trait Message { private $message; function alert() { $this->define(); echo $this->message; } abstract function define(); } class Messenger { use Message; function define() { $this->message = "Custom Message"; } } $messenger = new Messenger; $messenger->alert(); //Custom Message
Message
define()
总结
特征是 PHP 5.4 中引入的最强大的功能之一,我在本文中几乎讨论了它们的所有功能。它们允许程序员在多个类中水平重用代码片段,而这些类不必位于同一继承层次结构中。它们没有复杂的语义,而是为我们提供了一种轻量级的代码重用机制。尽管特征存在一些缺点,但它们肯定可以帮助改进应用程序的设计,消除代码重复并使其更加干燥。图片来自 Vlue / Shutterstock关于在 PHP 5.4 中使用 Traits 的常见问题 (FAQ)
在 PHP 5.4 中使用 traits 有什么好处?
PHP 5.4 中的 Traits 为代码的可重用性提供了强大的工具。它们允许开发人员在多个类中重用方法。特征可以包含在不同的类中,以访问同一组方法,从而减少代码重复。它们还通过允许类从多个特征继承方法来帮助解决 PHP 的单一继承问题。
如何在班级中使用特质?
要在类中使用特征,您需要在类中使用“use”关键字。下面是一个示例:
在此示例中,MyClass 现在可以使用 MyTrait 中定义的 myMethod() 函数。trait MyTrait {
public function myMethod() {
// method code
}
}
class MyClass {
use MyTrait;
}
特征可以覆盖类中的方法吗?
是的,特征可以覆盖类中的方法。如果特征和类具有同名的方法,则特征中的方法将覆盖类中的方法。但是,如果类使用具有相同名称的方法的多个特征,则由于不明确性,将发生致命错误。
如何解决性状之间的冲突?
PHP 提供了两种解决特征之间冲突的方法:“insteadof”运算符和方法别名。'insteadof' 运算符用于指定当两个特征具有同名方法时要使用的方法。方法别名允许您在使用特征的类中为特征中的方法指定不同的名称。
一个特征可以使用另一个特征吗?
是的,一个特征可以使用另一个特征。这是使用特征中的“use”关键字完成的。这允许您在另一个特征中重用一个特征的方法。
性状可以有属性吗?
是的,特征可以有属性。可以采用与类属性相同的方式访问和操作这些属性。
特征可以有构造函数吗?
特征不能有构造函数。如果需要初始化特征中的某些属性,可以定义 init() 方法或类似方法,并从使用该特征的类中调用它。
特质可以是抽象的吗?
不,特征不能是抽象的。但是,它们可以具有抽象方法。这些方法必须在使用特征的类中实现。
特征可以实现接口吗?
不可以,特征不能实现接口。但是,使用特征的类可以实现接口。
特征可以是静态的吗?
不,特征不能是静态的。但是,它们可以具有静态方法和属性。这些方法的访问方式与类中的静态方法和属性相同。
发表评论 取消回复