ModernPHP 性状

本文讲述了何谓性状,以及PHP中性状出现的意义,怎么去使用。

0x00 性状是什么

这是PHP5.4.0引入的概念,既像类又像接口。性状是类的部分实现(即常量,属性和方法),可以混入一个或多个类中。性状有两个作用:表名类可以做什么(像是接口),提供模块化的实现(像是类)。

0x01 性状的作用

PHP语言使用的是典型的继承模型。在这个模型中,我们先创建基类,实现基本的功能,然后扩展这个基类,通过继承基类创建更多具体的类。这叫做继承层次结构,很多编程语言都是用这个模式。

但是,如果想让两个无关的PHP类具有类型的行为,应该如何去做?例如,Satellite(卫星类)和Car(汽车类)两个类的作用十分不同,在继承层次结构上没有共同的父类,但是这两个类应该都能使用地理编码技术转换成经纬度,然后在地图上显示。

这时候就可以使用性状来解决。性状可以把模块化的实现方式注入到多个无关的类中,还能促进代码的重用。

0x02 性状的创建

定义一个性状:

1
2
3
4
<?php
trait MyTrait {
// 实现
}

我们接着使用上面提到的例子来演示如何使用性状。我们希望Satellite和Car类都能提供地理编码功能,而且意识到继承和接口都不是最佳方案。我们选择创建Geocodable(地理编码)性状,返回经纬度,然后在地图中绘制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php
trait Geocodable {
/* @var string */
protected $address;
/* @var \Geocoder\Geocoder */
protected $geocoder;
/* @var \Geocoder\Result\Geocoded */
protected $geocoderResult;

public function setGeocoder(\Geocoder\GeocoderInterface $geocoder)
{
$this->geocoder = $geocoder;
}

public function setAddress($address)
{
$this->address = $address;
}

public function getLatitude()
{
if(!isset($this->geocoderResult)) {
$this->geocodeAddress();
}

return $this->geocoderResult->getLatitude();
}

public function getLongitude()
{
if(!isset($this->geocoderResult)) {
$this->geocodeAddress();
}

return $this->geocoderResult->getLongitude();
}

protected function geocodeAddress()
{
$this->geocoderResult = $this->geocoder->geocode($this->address);

return true;
}
}

这个Geocodable性状定义了三个类属性:一个表示地址;一个是地理编码器对象(\Geocoder\Geocoder类的实例,这个类是来自第三方的组件);一个是地理编码器处理后的结果对象(\Geocoder\Result\Geocoded类的实例)。我们还定义了4个公有方法和一个受保护的方法。setGeocoder()方法用于注入Geocoder对象;setAddress()方法用于设定地址;getLatitude()和getLongitude()用于返回经纬度;geocodeAddress()方法把地址字符串传给Geocoder实例,获取地理编码器处理的结果。

0x03 性状的使用

1
2
3
4
5
<?php
class MyClass {
use MyTrait;
// 类的实现
}

这里需要注意,PHP解释器在编译的时候会把性状复制黏贴到类的定义体中,但是不会处理这个操作引入的不兼容问题。如果性状假定类中有特定的方法或属性(在性状中没有定义),要确保相应的类中有对应的属性和方法。

ModernPHP 系列全集:传送门

0%