rdl*_*rey 516
公共服务声明:
我想说明我认为特征几乎总是代码味道,应该避免使用构图.我认为单一继承经常被滥用到反模式,多重继承只会使这个问题复杂化.在大多数情况下,通过支持合成而不是继承(无论是单个还是多个),您将获得更好的服务.如果您仍然对特征及其与接口的关系感兴趣,请继续阅读......
我们先说这个:
面向对象编程(OOP)可能是一个难以掌握的范例.仅仅因为你正在使用类并不意味着你的代码是面向对象的(OO).
要编写OO代码,您需要了解OOP实际上是关于对象的功能.你必须从他们能做什么而不是他们实际做的事情来考虑课程.这与传统的程序编程形成鲜明对比,传统的程序编程的重点是使一些代码"做点什么".
如果OOP代码是关于规划和设计的,那么界面就是蓝图,而对象就是完全构建的房子.同时,特征只是帮助建立蓝图(界面)所构建的房屋的一种方式.
那么,我们为什么要使用接口呢?很简单,接口使我们的代码不那么脆弱.如果您怀疑此声明,请询问任何被迫维护未针对接口编写的遗留代码的人.
界面是程序员和他/她的代码之间的契约.界面说:"只要你按照我的规则玩,你就可以实现我,但我保证不会破坏你的其他代码."
举个例子,考虑一个真实的场景(没有汽车或小部件):
您希望为Web应用程序实现缓存系统以减少服务器负载
首先,使用APC编写一个类来缓存请求响应:
class ApcCacher
{
public function fetch($key) {
return apc_fetch($key);
}
public function store($key, $data) {
return apc_store($key, $data);
}
public function delete($key) {
return apc_delete($key);
}
}
Run Code Online (Sandbox Code Playgroud)
然后,在您的http响应对象中,在完成所有工作之前检查缓存命中以生成实际响应:
class Controller
{
protected $req;
protected $resp;
protected $cacher;
public function __construct(Request $req, Response $resp, ApcCacher $cacher=NULL) {
$this->req = $req;
$this->resp = $resp;
$this->cacher = $cacher;
$this->buildResponse();
}
public function buildResponse() {
if (NULL !== $this->cacher && $response = $this->cacher->fetch($this->req->uri()) {
$this->resp = $response;
} else {
// Build the response manually
}
}
public function getResponse() {
return $this->resp;
}
}
Run Code Online (Sandbox Code Playgroud)
这种方法效果很好.但也许几周后你决定要使用基于文件的缓存系统而不是APC.现在,您必须更改控制器代码,因为您已将控制器编程为使用ApcCacher类的功能,而不是使用表示ApcCacher类功能的接口.让我们说代替上面你已经让这个Controller类依赖于a CacherInterface而不是具体ApcCacher如此:
// Your controller's constructor using the interface as a dependency
public function __construct(Request $req, Response $resp, CacherInterface $cacher=NULL)
Run Code Online (Sandbox Code Playgroud)
为此,您可以像这样定义界面:
interface CacherInterface
{
public function fetch($key);
public function store($key, $data);
public function delete($key);
}
Run Code Online (Sandbox Code Playgroud)
反过来,您ApcCacher和您的新FileCacher类都可以实现,CacherInterface并且您可以对Controller类进行编程以使用接口所需的功能.
这个例子(希望如此)演示了如何对接口进行编程,允许您更改类的内部实现,而不必担心更改是否会破坏其他代码.
另一方面,特征只是一种重用代码的方法.不应将界面视为特征的互斥替代品.实际上,创建满足接口所需功能的特性是理想的用例.
您应该只在多个类共享相同功能时使用特征(可能由相同的接口决定).使用特征为单个类提供功能是没有意义的:它只会混淆类的功能,更好的设计会将特征的功能移动到相关的类中.
考虑以下特征实现:
interface Person
{
public function greet();
public function eat($food);
}
trait EatingTrait
{
public function eat($food)
{
$this->putInMouth($food);
}
private function putInMouth($food)
{
// Digest delicious food
}
}
class NicePerson implements Person
{
use EatingTrait;
public function greet()
{
echo 'Good day, good sir!';
}
}
class MeanPerson implements Person
{
use EatingTrait;
public function greet()
{
echo 'Your mother was a hamster!';
}
}
Run Code Online (Sandbox Code Playgroud)
一个更具体的例子:假设您FileCacher和您ApcCacher的界面讨论使用相同的方法来确定缓存条目是否陈旧并且应该被删除(显然现实情况并非如此,但请继续使用).您可以编写特征并允许两个类将其用于公共接口要求.
最后一点需要注意:小心不要过分贬低特质.当独特的类实现就足够了时,通常将traits用作糟糕设计的拐杖.您应该限制特性以满足最佳代码设计的接口要求.
Ale*_*rge 230
接口定义了实现类必须实现的一组方法.
当一个特征是use'd'时,方法的实现也会出现 - 这不会发生在Interface.
这是最大的不同.
Traits是一种在单继承语言(如PHP)中重用代码的机制.Trait旨在通过使开发人员能够在生活在不同类层次结构中的多个独立类中自由地重用方法集来减少单继承的某些限制.
Tro*_*ord 67
A trait本质上是PHP的a实现,实际上mixin是一组扩展方法,可以通过添加到任何类来添加trait.然后,这些方法成为该类实现的一部分,但不使用继承.
从PHP手册(强调我的):
特征是一种在单继承语言(如PHP)中重用代码的机制.......它是对传统继承的补充,可以实现行为的横向组合; 也就是说,类成员的应用程序不需要继承.
一个例子:
trait myTrait {
function foo() { return "Foo!"; }
function bar() { return "Bar!"; }
}
Run Code Online (Sandbox Code Playgroud)
通过定义上述特征,我现在可以执行以下操作:
class MyClass extends SomeBaseClass {
use myTrait; // Inclusion of the trait myTrait
}
Run Code Online (Sandbox Code Playgroud)
此时,当我创建一个类的实例时MyClass,它有两个方法,叫做foo()和bar()- 来自myTrait.并且 - 注意trait-defined方法已经有一个方法体 - 一个Interface--defined方法不能.
另外 - 与许多其他语言一样,PHP使用单个继承模型 - 这意味着类可以从多个接口派生,但不能从多个类派生.但是,PHP类可以有多个trait包含 - 允许程序员包含可重用的部分 - 因为它们可能包括多个基类.
有几点需要注意:
-----------------------------------------------
| Interface | Base Class | Trait |
===============================================
> 1 per class | Yes | No | Yes |
---------------------------------------------------------------------
Define Method Body | No | Yes | Yes |
---------------------------------------------------------------------
Polymorphism | Yes | Yes | No |
---------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)
多态性:
在前面的示例,其中,MyClass 延伸 SomeBaseClass,MyClass 是一个实例SomeBaseClass.换句话说,一个数组如SomeBaseClass[] bases可以包含实例MyClass.同样,如果MyClass扩展IBaseInterface,则数组IBaseInterface[] bases可以包含实例MyClass.没有这样的多态结构可用trait- 因为a trait本质上只是代码,为程序员的方便而复制到使用它的每个类中.
优先级:
如手册中所述:
来自基类的继承成员被特征插入的成员覆盖.优先顺序是来自当前类的成员重写Trait方法,这些方法返回覆盖继承的方法.
所以 - 考虑以下场景:
class BaseClass {
function SomeMethod() { /* Do stuff here */ }
}
interface IBase {
function SomeMethod();
}
trait myTrait {
function SomeMethod() { /* Do different stuff here */ }
}
class MyClass extends BaseClass implements IBase {
use myTrait;
function SomeMethod() { /* Do a third thing */ }
}
Run Code Online (Sandbox Code Playgroud)
在上面创建MyClass的实例时,会发生以下情况:
Interface IBase要求被称为参数的功能SomeMethod()来提供.BaseClass提供了此方法的实现 - 满足需要.trait myTrait提供所谓的无参数的函数SomeMethod(),以及,其优先在BaseClass-versionclass MyClass提供了自己的版本SomeMethod()- 它优先于trait-version.结论
Interfacecan不能提供方法体的默认实现trait.Interface是一个多态的,继承的构造 - 而a trait则不是.Interfaces可以在同一个类中使用,因此可以使用多个traits.J. *_*uni 26
我认为traits创建包含可用作多个不同类的方法的方法的类很有用.
例如:
trait ToolKit
{
public $errors = array();
public function error($msg)
{
$this->errors[] = $msg;
return false;
}
}
Run Code Online (Sandbox Code Playgroud)
您可以在使用此特征的任何类中使用此"错误"方法.
class Something
{
use Toolkit;
public function do_something($zipcode)
{
if (preg_match('/^[0-9]{5}$/', $zipcode) !== 1)
return $this->error('Invalid zipcode.');
// do something here
}
}
Run Code Online (Sandbox Code Playgroud)
虽然interfaces你只能声明方法签名,但不能声明其函数的代码.此外,要使用界面,您需要遵循层次结构,使用implements.特征不是这种情况.
它完全不同!
Sup*_*eth 18
对于初学者来说,上面的答案可能很难,这是理解它的最简单方法:
性状
trait SayWorld {
public function sayHello() {
echo 'World!';
}
}
Run Code Online (Sandbox Code Playgroud)
因此,如果你想sayHello在其他类中使用函数而不重新创建整个函数,你可以使用特征,
class MyClass{
use SayWorld;
}
$o = new MyClass();
$o->sayHello();
Run Code Online (Sandbox Code Playgroud)
好吧!
不仅可以在特征中使用任何函数(函数,变量,常量......).你也可以使用多种特质:use SayWorld,AnotherTraits;
接口
interface SayWorld {
public function sayHello();
}
class MyClass implements SayWorld {
public function sayHello() {
echo 'World!';
}
}
Run Code Online (Sandbox Code Playgroud)
所以这就是界面与特征的不同之处:你必须在实现的类中重新创建界面中的所有东西.接口没有实现.和接口只能有函数和const,它不能有变量.
我希望这有帮助!
Traits只是为了代码重用。
接口仅提供要在类中定义的函数的签名,可以根据程序员的判断来使用该函数。从而为我们提供了一组类的原型。
供参考 - http://www.php.net/manual/en/language.oop5.traits.php
描述 Traits 的一个常用比喻是 Traits 是与实现的接口。
在大多数情况下,这是一种很好的思考方式,但两者之间存在许多细微差别。
首先,instanceof操作符不会处理特征(即特征不是真正的对象),因此您不能使用它来查看一个类是否具有某个特征(或查看两个不相关的类是否共享一个特征)。这就是他们所说的水平代码重用结构。
PHP 中现在有一些函数可以让您获得一个类使用的所有特征的列表,但是 trait-inheritance 意味着您需要进行递归检查以可靠地检查某个类是否具有特定的特征(有示例PHP doco 页面上的代码)。但是,是的,它肯定不像现在那么简单和干净instanceof,恕我直言,这是一个可以让 PHP 变得更好的功能。
此外,抽象类仍然是类,因此它们不能解决与多继承相关的代码重用问题。请记住,您只能扩展一个类(真实的或抽象的)但可以实现多个接口。
我发现特质和接口非常适合用于创建伪多重继承。例如:
class SlidingDoor extends Door implements IKeyed
{
use KeyedTrait;
[...] // Generally not a lot else goes here since it's all in the trait
}
Run Code Online (Sandbox Code Playgroud)
这样做意味着您可以使用instanceof来确定特定的 Door 对象是否是 Keyed 的,您知道您将获得一组一致的方法等,并且所有代码都在使用 KeyedTrait 的所有类中的一个地方。