Php 特征与界面

Php 特征与界面,php,interface,traits,Php,Interface,Traits,最近我一直在努力学习PHP,我发现自己对特性很感兴趣。我理解水平代码重用的概念,不想从抽象类继承。我不明白的是:使用特性和界面之间的关键区别是什么 我试着搜索一篇像样的博客文章或文章,解释何时使用其中一种,但我发现迄今为止的示例似乎非常相似,完全相同。接口定义了一组实现类必须实现的方法 当一个特征是使用”时,这些方法的实现也会出现——这在接口中不会发生 这是最大的区别 从: Traits是单继承语言(如PHP)中的代码重用机制。Trait旨在通过使开发人员能够在不同类层次结构中的几个独立类中自由

最近我一直在努力学习PHP,我发现自己对特性很感兴趣。我理解水平代码重用的概念,不想从抽象类继承。我不明白的是:使用特性和界面之间的关键区别是什么


我试着搜索一篇像样的博客文章或文章,解释何时使用其中一种,但我发现迄今为止的示例似乎非常相似,完全相同。

接口定义了一组实现类必须实现的方法

当一个特征是
使用
”时,这些方法的实现也会出现——这在
接口
中不会发生

这是最大的区别

从:

Traits是单继承语言(如PHP)中的代码重用机制。Trait旨在通过使开发人员能够在不同类层次结构中的几个独立类中自由地重用方法集来减少单个继承的一些限制


我认为
traits
对于创建包含方法的类非常有用,这些方法可以用作几个不同类的方法

例如:

trait ToolKit
{
    public $errors = array();

    public function error($msg)
    {
        $this->errors[] = $msg;
        return false;
    }
}
您可以在任何使用此特性的类中使用此“error”方法

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
    }
}
使用
接口时
只能声明方法签名,而不能声明其函数的代码。此外,要使用接口,您需要遵循层次结构,使用
实现
。这与性格无关


这是完全不同的

公共服务公告:

                      -----------------------------------------------
                      |   Interface   |  Base Class   |    Trait    |
                      ===============================================
> 1 per class         |      Yes      |       No      |     Yes     |
---------------------------------------------------------------------
Define Method Body    |      No       |       Yes     |     Yes     |
---------------------------------------------------------------------
Polymorphism          |      Yes      |       Yes     |     No      |
---------------------------------------------------------------------
我想记录在案,我相信特质几乎总是一种代码味道,应该避免使用,而应该使用组合。我认为单一继承经常被滥用到反模式的地步,而多重继承只会加剧这个问题。在大多数情况下,通过支持组合而不是继承(无论是单个还是多个),您将得到更好的服务。如果你仍然对特性及其与接口的关系感兴趣,请继续阅读


让我们首先说:

面向对象编程(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);
  }
}
然后,在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;
  }
}
这种方法非常有效。但也许几周后,您决定使用基于文件的缓存系统,而不是APC。现在您必须更改控制器代码,因为您已将控制器编程为使用
ApcCacher
类的功能,而不是使用表示
ApcCacher
类功能的接口。假设您使
控制器
类依赖于
缓存接口
,而不是具体的
ApcCacher
,而不是像这样:

// Your controller's constructor using the interface as a dependency
public function __construct(Request $req, Response $resp, CacherInterface $cacher=NULL)
接下来,您可以这样定义接口:

interface CacherInterface
{
  public function fetch($key);
  public function store($key, $data);
  public function delete($key);
}
反过来,您的
ApcCacher
和新的
FileCacher
类都实现了
cacherface
,您可以对
控制器
类进行编程,以使用接口所需的功能

这个例子(希望如此)演示了编程到接口如何允许您更改类的内部实现,而不必担心这些更改是否会破坏其他代码

特点 另一方面,Traits只是一种重用代码的方法。接口不应该被认为是特性的互斥替代品。事实上,创建满足接口所需功能的特性是理想的用例

只有当多个类共享相同的功能(可能由相同的接口指定)时,才应该使用traits。使用trait为单个类提供功能是没有意义的:这只会混淆类的功能,更好的设计会将trait的功能转移到相关类中

考虑以下特性实现:

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!';
    }
}
一个更具体的例子:假设您的
文件缓存器
和接口讨论中的
ApcCacher
都使用相同的方法来确定缓存项是否过时,是否应该删除(很明显,现实生活中不是这样,但请继续)。您可以编写一个trait,并允许两个类都使用它来满足公共接口需求

最后一点要注意的是:小心不要过分强调个性特征。通常使用特征
trait myTrait {
    function foo() { return "Foo!"; }
    function bar() { return "Bar!"; }
}
class MyClass extends SomeBaseClass {
    use myTrait; // Inclusion of the trait myTrait
}
                      -----------------------------------------------
                      |   Interface   |  Base Class   |    Trait    |
                      ===============================================
> 1 per class         |      Yes      |       No      |     Yes     |
---------------------------------------------------------------------
Define Method Body    |      No       |       Yes     |     Yes     |
---------------------------------------------------------------------
Polymorphism          |      Yes      |       Yes     |     No      |
---------------------------------------------------------------------
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 */ }
}
interface Observable {
    function addEventListener($eventName, callable $listener);
    function removeEventListener($eventName, callable $listener);
    function removeAllEventListeners($eventName);
}
$auction = new Auction();

// Add a listener, so we know when we get a bid.
$auction->addEventListener('bid', function($bidderName, $bidAmount){
    echo "Got a bid of $bidAmount from $bidderName\n";
});

// Mock some bids.
foreach (['Moe', 'Curly', 'Larry'] as $name) {
    $auction->addBid($name, rand());
}
class EventEmitter {
    private $eventListenersByName = [];

    function addEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName][] = $listener;
    }

    function removeEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName] = array_filter($this->eventListenersByName[$eventName], function($existingListener) use ($listener) {
            return $existingListener === $listener;
        });
    }

    function removeAllEventListeners($eventName) {
        $this->eventListenersByName[$eventName] = [];
    }

    function triggerEvent($eventName, array $eventArgs) {
        foreach ($this->eventListenersByName[$eventName] as $listener) {
            call_user_func_array($listener, $eventArgs);
        }
    }
}

class Auction implements Observable {
    private $eventEmitter;

    public function __construct() {
        $this->eventEmitter = new EventEmitter();
    }

    function addBid($bidderName, $bidAmount) {
        $this->eventEmitter->triggerEvent('bid', [$bidderName, $bidAmount]);
    }

    function addEventListener($eventName, callable $listener) {
        $this->eventEmitter->addEventListener($eventName, $listener);
    }

    function removeEventListener($eventName, callable $listener) {
        $this->eventEmitter->removeEventListener($eventName, $listener);
    }

    function removeAllEventListeners($eventName) {
        $this->eventEmitter->removeAllEventListeners($eventName);
    }
}
trait EventEmitterTrait {
    private $eventListenersByName = [];

    function addEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName][] = $listener;
    }

    function removeEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName] = array_filter($this->eventListenersByName[$eventName], function($existingListener) use ($listener) {
            return $existingListener === $listener;
        });
    }

    function removeAllEventListeners($eventName) {
        $this->eventListenersByName[$eventName] = [];
    }

    protected function triggerEvent($eventName, array $eventArgs) {
        foreach ($this->eventListenersByName[$eventName] as $listener) {
            call_user_func_array($listener, $eventArgs);
        }
    }
}

class Auction implements Observable {
    use EventEmitterTrait;

    function addBid($bidderName, $bidAmount) {
        $this->triggerEvent('bid', [$bidderName, $bidAmount]);
    }
}
trait SayWorld {
    public function sayHello() {
        echo 'World!';
    }
}
class MyClass{
  use SayWorld;

}

$o = new MyClass();
$o->sayHello();
  interface SayWorld {
     public function sayHello();
  }

  class MyClass implements SayWorld { 
     public function sayHello() {
        echo 'World!';
     }
}