使用特性
如果你曾经做过任何C语言编程,你也许会熟悉宏。宏是一个预定义的代码块,它在指定的行处展开。类似地,特性可以包含代码块,这些代码块在PHP解释器指定的行处被复制并粘贴到一个类中。
如何做...
1.特性用关键字 trait
标识,可以包含属性和方法。你可能已经注意到,在检查之前具有 CountryList
和 CustomerList
类的案例时,有重复的代码。在这个例子中,我们将重构这两个类,并将 list()
方法的功能移到Trait
中。请注意,两个类中的 list()
方法是一样的。
2.特性用于类与类之间代码重复使用的情况。但请注意,传统的创建抽象类并扩展它的方法可能比 traits
更可能有某些优势。特性不能用来确定继承的线路,而抽象父类可以用于这个目的。
3.现在,我们将 list()
复制到一个名为 ListTrait
的特性中:
4.然后,我们可以将 ListTrait
中的代码插入到新类 CountryListUsingTrait
中,如以下代码片段所示。 现在可以从此类中删除整个 list()
方法:
每当您有重复的代码进行更改时就会出现潜在的问题。 您可能会发现自己不得不进行过多的全局搜索和替换操作,或者剪切和粘贴代码,而结果往往是灾难性的。 特性是避免这种维护噩梦的好方法。
5.特性受命名空间影响。 在步骤1所示的示例中,如果将新的 CountryListUsingTrait
类放置在命名空间 Application\Generic
中,则我们还需要将 ListTrait
也一起移入该命名空间:
6. 特性中的方法将覆盖继承的方法。
7.在下面的例子中,您会注意到 setId()
方法的返回值在 Base
父类和 Test
特性之间有所不同。Customer
类继承自 Base
,但也使用 Test
。在这种情况下,特性中定义的方法将覆盖 Base
父类中定义的方法:
在 PHP 5 中,traits
也可以覆盖属性。在 PHP 7 中,如果 trait 中的属性初始化为与父类不同的值,会产生一个致命的错误。
8. 在类中定义了与特性中一样的方法,则在引入特性的时候了类中的方法将覆盖特性中的方法。
9.在这个例子中,Test
特性定义了一个属性 $id
以及 getId()
和setId()
方法。该特性还定义了 setName()
,它与 Customer
类中定义的相同方法有冲突。在这种情况下,Customer
类中直接定义的 setName()
方法将覆盖特性中定义的 setName()
:
10.当使用多个特性时,使用 insteadof
关键字来解决方法名冲突。结合使用 as
关键字来对方法名进行别名设置。
11.在这个例子中,有两个特性,IdTrait
和 NameTrait
。这两个特性都定义了一个 setKey()
方法,但是使用了不同的方式来表达key。Test
类使用了这两个特性。注意 insteadof
关键字,它允许我们区分冲突的方法。因此,当从 Test
类调用 setKey()
时,源将从 NameTrait
中抽取。此外,来自 IdTrait
的 setKey()
仍将可用,但用的是别名setKeyDate()
:
如何运行...
从步骤1开始,您了解到特性用于出现重复代码的情况下。 您需要评估是否可以简单地定义一个基类并扩展它,或者使用特性是否更好地满足您的目的。 当在逻辑上不相关的类中看到代码重复时,特性将特别有用。
为了说明特性方法如何覆盖继承的方法,请将步骤7中提到的代码块复制到一个单独的文件 chap_04_oop_traits_override_inherited.php
中。 添加以下代码行:
从输出中可以看到(下图所示),属性 $id
中存储了 stdClass()
的实例,这就是在特性中定义的行为:
为了说明直接定义的类方法如何覆盖特性方法,请将步骤9中提到的代码块复制到单独的文件 chap_04_oop_trait_methods_do_not_override_class_methods.php
中。 添加以下代码行:
从以下输出中可以看到,$id
属性存储为整数,如在 Customer
类中定义的,而特性将 $id
定义为 stdClass
的实例:
在步骤10中,您学习了如何在使用多个特性时解决重复的方法名称冲突。 将步骤11中显示的代码块复制到单独的文件 chap_04_oop_trait_multiple.php
中。 添加以下代码:
请注意,在下面的输出中,setKey()
返回的数据是PHP 7新函数random_bytes()
(在 NameTrait中定义
)产生的,而 setKeyDate()
返回的数据是使用 date()
和 rand()
函数(在 IdTrait
中定义)产生的:
最后更新于