现代PHP的新特性(5) – Traits

Traits

Many of my PHP developer friends are confused by traits,a new concept introduced in PHP 5.4.0. Traits behave like classes but look like interfaces.witch one are they?neither and both.
很多PHP开发者都对traits感到困惑,这是PHP5.4引入的一个新概念,trains看上去和类差不多,但是实际表现更接近接口,实际上traits与类和接口有相似之处又有不同之处.

A trait is a partial class implementation(i.e,constants,properties,and methods)that can be mixed into one or more existing PHP classes.traits work double duty:they say what a class can do(like an interface),and they provide a modular implementation(like a class).

Trait在某些部分与类的实现可以结合在一个或多个存在的PHP类中.traits有多重用途:声明类可以做什么(类似于接口),而且他也能提供模块化的功能实现(和类一样)

You may be familiar with traits in other languages.for example,PHP traits are similar to Ruby’s composable modules.or mixins.

你可能在其他语言中对traints有更多的见解,PHP的traits与Ruby的组合方式相比更加精简.

Why we use traits
为什么要使用traits

The PHP language uses a classical inheritance model.this means you start with a single generalized root class that provides a base implementation.you extend the root class to create more specialized classes that inherit their immediate parent’s implementation.this is called an inheritance hierarchy,and it is a common pattern used by many programming languages.
PHP使用传统的继承模型,你从一个基类开始实现功能,并基于基类创建更多的继承类,继承类拥有父类的功能,这称为继承层级,这种模式在很多语言中都有实现.

If it helps,picture yourself back in grade school biology.remember how you learned about the biological classification system?there are six kingdom is extended by phyla.each phylum is extended by biological classes.classes are extended by orders,orders by families,families by genera,and genera by species.each hierarchy extension represents further specialization

这能给我们带来什么帮助呢?回想我们上小学时,你是怎么学习生物的,生物衍生出6个领域,每个领域类继承至生物类,每个层级都延伸至更细致的领域.

The classical inheritance model works well most of the time.however,what do we do if two unrelated PHP classes need to exhibit similar behavior?for example,a PHP class retailsrore and another PHP class car are very different classes and don’t share a common parent in their inheritance hierarchies.however,both classes should be geocodable into latitude and longitude coordinates for display on a map.
传统的继承模型能够应付大多数大多数情况,不过怎样才能让两个不相关的PHP类表现出相似的行为呢?举个例子,一个PHP类RetailStore和另一个PHP类Car是不同的类,同时他们也没有继承层级中也没有公共的基类,这两个类都需要被Geocodable类继承以便为地图显示经显示经纬度坐标.
Traits were created for exactly this purpose.they enable modular implementations that can be injected into otherwise unrelated classes.traits also encourage code reuse.
Traits正好可以解决这个问题,他允许注入其他类组合实现某个功能,以实现代码复用

My first(bad) reaction is to create a common parent class geocodable that both retailstore and car extend.this is a bad solution because it forces two otherwise unrelated classes to share a common ancestor that does not naturally belong in either inheritance hierarchy.

我的第一反应(不好的做法)是为geocodable创建一个公共的基类,让retailStore和car类继承他,这是一个不好的解决方式,因为这强制让两个不相干的类继承一个基类,这使得继承层级不够自然.

My second(better)reaction is to create a Geocodable interface that defines which methods are required to implement the Geocodable behavior.the retailStore and Car classes can both implement the Geocodable interface.this is a good solution that allows each class to retain its natural inheritance hierarchy,but it requires us to duplicate the same Geocodable behavior in both classes,this is not a DRY solution.

我的第二个方案(一般的做法)是创建一个Geocodable类的接口,定义他需要的方法并在Geocodeable类中声明,retailStore和Car类同样也声明Geocodable的接口,这是一个比较好的解决方案,他允许每个类保留他们自己自然的继承层级,但是在两个类中包含了重复的Geocodable行为定义,这样不符合DRY原则.

DRY is an acronym for do not repeat yourself.it’s considered a good practice never to duplicate the same code in multiple locations.you should not need to change code in one location because you changed code in another location.

DRY是”不要重复自己”的缩写,他认为在项目中不要出现两处同样的代码,修改代码时就无需同时修改两个或更多的地方.

My third(best)reaction is to create a Geocodable trants that defined and implements the Geocodable methods.I can hen mix the Geocodable trait into both the RetailStore and Car classes without polluting their natural inheritance hierarchies.

我的第三个方案(最佳)是为Geocodable建立一个trait,并定义和继承Geocodable所需的方法,我可以在Geocodable的trait中混合retailStore和car类,而无需担心代码污染并保持他们继承层级的自然.

How to create a trait
如何创建一个trait

Here’s how you define a PHP trait:
下面是如何在PHP定义一个trait

<?php
Trait mytrait{
//trait implementation goes here
}

It is considered a good practice to define only one trait per file,just like class and interface definitions.
这个原型是定义trait的较好实践,就像定义类和接口一样

Let’s return to our geocodable example to better demonstrate traits in practice.we agree both RetailStore and Car classes need to provide geocodable behavior,and we’ve decided inheritance and interfaces are not the best solution.instead,we create a geocodable trait that returns latitude and longitude coordinates that we can plot on a map.our complete Geocodable trait looks like Example 2-12

我们回到geocodable实例,在较好的解决方案中,我们明白RetailStore和Car类都需要提供给Geocodable行为,同时我们也明白继承和接口不是最好的解决方案,我们为Geocodable创建trait返回经纬度功能,这样我们就可以绘制地图了.我们编写的Geocodable trait见示例2-12

<?php
Trait Geocodable{

Protected $address;
Protected $geocoder;
Protected $geocoderResult;

Public function setGeoer(\Grocoder\GeocoderInterface $geocoder)
{
$this->geocoder = $geocoder;
}

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

Public function getLatitude()
{
If(isset($this->geocodableResult) === false) $this->geocodAddress();
Return $this->geocoderResult->getLatitude();
}

Public function getLongitude()
{
If(isset($this->getcoderResult) === false) $this->geocodeAddress();
Return $this->getcodeAddress();
}

Protected function getcodeAddress()
{
$this->geocoderResult = $this->geocoder->geocode($ths->address);
Return ture;
}

The Geocodable trait defines only the properties and method necessary to implement the geocodable behavior.it does not do anything else.
Geocodable trait仅仅定义了特性和必要的方法去实现geocodable的行为,除此以外不需要做任何事.

Our geocodable trait defines three class properties:an address(string),a geocoder object(an instance of \Geocoder\Geocoder from the excellent willdurand/geocoder component by william durand),we also define four public methods and one protected method.the setGeocoder() method is used to inject the Geocoder object.the setAddress() method is used to set an address.the getLatitude() and getLongitude() methods return their respective coordinates.and the geocodeaddress() method passes the address string into the geocoder instance to retrieve the geocoder result.

我们的Geocodable trait定义了三个类属性: address(string) 一个geocodable对象(\geocoder\geocoder实例,他来自卓越的willdurand/geocoder组件 由 williamdurand编写),我们同样定义了4个公开方法和一个私有方法,setGeocoder()方法用来添加Geocoder对象,setAddress方法用于设置地址,getLatitude方法将将添加的地址字符串注入geocoder实例用于获取geocoder的结果.

How to use a trait
Using a PHP trait is easy.add the code use MyTrait;inside a PHP class definition.here’s an example.obviously,replace MyTrait with the appropriate PHP trait name:

如何使用trait
在PHP使用trait是很简单,如果我们要使用一个trait MyTrait,只需把他添加进PHP类定义中,这里有一个实例,显然MyTrait是一个恰当的trait名称

<?php

Class MyClass
{
Use MyTrait;
//class implementation goes here
}

Both namespaces and traits are imported with the use keyword.where they are imported is different.we import namespaces,classes,interfaces,functions,and constants outside of a class definition.we import traits inside a class definition.the difference is subtle but important.

命名空间和traits都通过关键词导入进来,不过他们的导入方式是不同的,我们导入命名空间,类,接口,函数和常量都是在类定义之外,而导入traits是在类定义之内,他们的差别很微妙但很重要.

Let’s return to our geocodable example.we defined the geocodable trait in Example 2-12 let’s update our retailstore class so that it uses the geocodable trait example 2-13.for the sake of brevity,I do not provide the complete retailstore class implementation.

我们回到我们的geocodable实例,我们在实例2-12定义了geocodable trait,我们更新retailstore类使其可以使用实例2-13的geocodable trait,原理很简单,所以我不在retailstore类提供具体的实现.

Example 2-13 the retailstore class definition
实例2-13 retailstore类的定义

<?php

Class retailstore
{
Use geocodable;
//class implementation goes here
}

That’s all we have to do.now each retailstore instance can use the properties and methods provided by the geocodable trait,as shown in Example 2-14

现在一切都搞定了,任何一个retailstore实例都可以使用geocodable的属性和方法,如同示例2-14展现的一样

Example 2-14 traits

<?php
$geocoderAdapter = new \geocoder\httpadapter\curlhttpadapter();
$geocoderProvide = new \geocoder\provider\googlemapsprovider($geocoderAdapter);
$geocoder = new \geocoder\geocoder($geocoderProvider);

$store = new trtailstore();
$store->setAddress(‘420 9th aveue, new york,Ny 10001 usa’);
$store->setGeocoder($geocoder);

$latitude = $store->getLatitude();
$longitude = $store->getLongtude();
Echo $latitude.’:’.$longitude;

The php interpreter copies and pastes trains into class definitions at compile time,and it does not protect against incompatobilities introduced by this action,if your PHP trait assumes a class property or method exits (that is not defined in the trait itself),be sure those propertied and methods exist in the appropriate classes.

PHP编译器在编译代码时会在类定义中复制并解析trains,但编译器在进行这个动作时并没有对兼容性进行保护,如果你的PHP trait试图获取了一个类已存在的属性或方法(这个属性或方法并没有在trait中定义),请保证这些属性或方法存在与能够获取到的类中.