Traços x interfaces

344

Ultimamente, tenho tentado estudar PHP e acabo ficando preso em traços. Entendo o conceito de reutilização de código horizontal e não quero necessariamente herdar de uma classe abstrata. O que não entendo é: Qual é a diferença crucial entre o uso de características versus interfaces?

Tentei procurar por um artigo ou artigo decente explicando quando usar um ou outro, mas os exemplos que encontrei até agora parecem tão parecidos quanto idênticos.

datguywhowanders
fonte
6
A interface não possui nenhum código nos corpos das funções. eles realmente não têm nenhum corpo de função.
hakre
2
Apesar da minha resposta muito votada, eu gostaria que fosse declarado que sou geralmente anti-trait / mixin . Verifique esta transcrição do bate-papo para ler como os traços frequentemente prejudicam práticas sólidas de POO .
Rdlowrey
2
Eu argumentaria o contrário. Tendo trabalhado com PHP há anos antes e desde o advento das características, acho fácil provar seu valor. Basta ler este exemplo prático que permite que os 'modelos de imagem' também andem e falem como Imagickobjetos, menos todo o inchaço necessário nos velhos tempos anteriores às características.
precisa
Traços e interface são semelhantes. A principal diferença é que os Traits permitem implementar os métodos, a Interface não.
John

Respostas:

238

Uma interface define um conjunto de métodos que a classe de implementação deve implementar.

Quando uma característica é encontrada use, as implementações dos métodos também aparecem - o que não acontece em uma Interface.

Essa é a maior diferença.

Na reutilização horizontal para RFC PHP :

Traits é um mecanismo para reutilização de código em linguagens de herança única, como PHP. Um Trait visa reduzir algumas limitações de herança única, permitindo que um desenvolvedor reutilize conjuntos de métodos livremente em várias classes independentes que vivem em diferentes hierarquias de classes.

Alec Gorge
fonte
2
@JREAM Na prática, nada. Na realidade, muito mais.
Alec Gorge
79
Exceto que os traços não são interfaces. Interfaces são especificações que podem ser verificadas. As características não podem ser verificadas, portanto, são apenas implementação. Eles são exatamente o oposto das interfaces. Essa linha no RFC é simplesmente errado ...
ircmaxell
195
Os traços são essencialmente copiados e colados com auxílio do idioma .
Shahid
10
Isso não é uma metáfora. Isso está massacrando o significado de uma palavra. É como descrever uma caixa como uma superfície com volume.
cleong
6
Para expandir os comentários de ircmaxell e Shadi: Você pode verificar se um objeto implementa uma interface (via instanceof) e pode garantir que um argumento de método implemente uma interface por meio de uma dica de tipo na assinatura do método. Você não pode executar verificações correspondentes para características.
Brian D'Astous
530

Anúncio de serviço público:

Quero declarar que acredito que os traços são quase sempre um cheiro de código e devem ser evitados em favor da composição. É minha opinião que a herança única é frequentemente abusada a ponto de ser um antipadrão e a herança múltipla apenas compõe esse problema. Você será muito melhor atendido na maioria dos casos, favorecendo a composição sobre a herança (seja ela única ou múltipla). Se você ainda está interessado em características e sua relação com interfaces, continue lendo ...


Vamos começar dizendo o seguinte:

A Programação Orientada a Objetos (OOP) pode ser um paradigma difícil de entender. Só porque você está usando classes, não significa que seu código seja Orientado a Objetos (OO).

Para escrever código OO, você precisa entender que OOP é realmente sobre os recursos de seus objetos. Você precisa pensar nas aulas em termos do que elas podem fazer, e não do que realmente fazem . Isso contrasta fortemente com a programação processual tradicional, na qual o foco é fazer um pouco de código "fazer alguma coisa".

Se o código OOP é sobre planejamento e design, uma interface é o blueprint e um objeto é a casa totalmente construída. Enquanto isso, os traços são simplesmente uma maneira de ajudar a construir a casa estabelecida pelo projeto (a interface).

Interfaces

Então, por que devemos usar interfaces? Simplesmente, as interfaces tornam nosso código menos quebradiço. Se você duvida desta afirmação, pergunte a alguém que foi forçado a manter o código legado que não foi escrito nas interfaces.

A interface é um contrato entre o programador e seu código. A interface diz: "Enquanto você seguir minhas regras, poderá me implementar da maneira que quiser e prometo que não quebrarei seu outro código".

Por exemplo, considere um cenário do mundo real (sem carros ou widgets):

Você deseja implementar um sistema de armazenamento em cache para um aplicativo Web reduzir a carga do servidor

Você começa escrevendo uma classe para armazenar em cache as respostas das solicitações usando a 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);
  }
}

Em seguida, no seu objeto de resposta HTTP, verifique se há um acerto no cache antes de fazer todo o trabalho para gerar a resposta real:

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;
  }
}

Essa abordagem funciona muito bem. Mas talvez algumas semanas depois você decida usar um sistema de cache baseado em arquivo em vez da APC. Agora você precisa alterar o código do seu controlador, porque o programou para trabalhar com a funcionalidade da ApcCacherclasse, e não com uma interface que expresse os recursos da ApcCacherclasse. Digamos que, em vez do acima, você tenha tornado a Controllerclasse dependente de um em CacherInterfacevez do concreto, da seguinte ApcCacherforma:

// Your controller's constructor using the interface as a dependency
public function __construct(Request $req, Response $resp, CacherInterface $cacher=NULL)

Para acompanhar, você define sua interface da seguinte maneira:

interface CacherInterface
{
  public function fetch($key);
  public function store($key, $data);
  public function delete($key);
}

Por sua vez, você ApcCachere suas novas FileCacherclasses implementam CacherInterfacee você programa sua Controllerclasse para usar os recursos exigidos pela interface.

Este exemplo (espero) demonstra como a programação em uma interface permite alterar a implementação interna de suas classes sem se preocupar se as alterações quebrarão seu outro código.

Traits

Traços, por outro lado, são simplesmente um método para reutilizar o código. As interfaces não devem ser pensadas como uma alternativa mutuamente exclusiva às características. De fato, a criação de características que atendam aos recursos exigidos por uma interface é o caso de uso ideal .

Você só deve usar características quando várias classes compartilham a mesma funcionalidade (provavelmente ditada pela mesma interface). Não faz sentido usar uma característica para fornecer funcionalidade para uma única classe: isso apenas ofusca o que a classe faz e um design melhor transfere a funcionalidade da característica para a classe relevante.

Considere a seguinte implementação de característica:

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!';
    }
}

Um exemplo mais concreto: imagine que você FileCachere seu ApcCacherda discussão da interface usem o mesmo método para determinar se uma entrada de cache é obsoleta e deve ser excluída (obviamente esse não é o caso na vida real, mas vá com ela). Você pode escrever uma característica e permitir que ambas as classes a usem para o requisito de interface comum.

Uma última palavra de cautela: tenha cuidado para não exagerar nas características. Muitas vezes, as características são usadas como muleta para o design deficiente quando implementações de classe exclusivas são suficientes. Você deve limitar as características ao cumprimento dos requisitos da interface para obter o melhor design de código.

rdlowrey
fonte
69
Eu estava realmente procurando pela resposta rápida e simples que foi fornecida acima, mas devo dizer que você deu uma excelente resposta detalhada que ajudará a tornar a distinção mais clara para os outros, parabéns.
datguywhowanders
35
"[C] características de montagem que atendem aos recursos exigidos por uma interface em uma determinada classe é um caso de uso ideal". Exatamente: +1
Alec Gorge
5
Seria justo dizer que os traços no PHP são semelhantes aos mixins em outros idiomas?
Eno
5
@igorpan Para todos os efeitos, eu diria que a implementação de características do PHP é a mesma que herança múltipla. Vale a pena notar que, se uma característica no PHP especificar propriedades estáticas, cada classe que usar a característica terá sua própria cópia da propriedade estática. Mais importante ... vendo como esta postagem agora é extremamente alta nos SERPs ao consultar características, vou adicionar um anúncio de serviço público na parte superior da página. Você deveria ler.
Rdlowrey #
3
+1 para uma explicação detalhada. Eu venho de um fundo rubi, onde mixins são muito utilizados; apenas para adicionar meus dois centavos, uma boa regra geral que usamos pode ser traduzida em php como "não implemente métodos que modifiquem $ this nas características". Isso evita um monte de sessões malucas de depuração ... Um mixin também NÃO deve fazer suposições sobre a classe na qual ele será misturado (ou você deve deixar bem claro e reduzir as dependências a um mínimo). A esse respeito, acho agradável sua ideia de características implementando interfaces.
M_x 11/04
67

A traité essencialmente a implementação de a de PHP mixine é efetivamente um conjunto de métodos de extensão que podem ser adicionados a qualquer classe através da adição de trait. Os métodos então se tornam parte da implementação dessa classe, mas sem usar herança .

No Manual do PHP (ênfase minha):

Os traços são um mecanismo para a reutilização de código em linguagens de herança única, como PHP. ... É uma adição à herança tradicional e permite a composição horizontal do comportamento; isto é, a aplicação dos membros da classe sem exigir herança.

Um exemplo:

trait myTrait {
    function foo() { return "Foo!"; }
    function bar() { return "Bar!"; }
}

Com a característica acima definida, agora posso fazer o seguinte:

class MyClass extends SomeBaseClass {
    use myTrait; // Inclusion of the trait myTrait
}

Neste ponto, quando eu crio uma instância da classe MyClass, ela tem dois métodos, chamados foo()e bar()- que vêm myTrait. E - observe que os traitmétodos definidos já têm um corpo de método - que um Interfacemétodo definido não pode.

Além disso, o PHP, como muitas outras linguagens, usa um único modelo de herança - o que significa que uma classe pode derivar de várias interfaces, mas não de várias classes. No entanto, uma classe PHP pode ter várias traitinclusões - o que permite que o programador inclua partes reutilizáveis ​​- como poderiam incluir várias classes base.

Algumas coisas a serem observadas:

                      -----------------------------------------------
                      |   Interface   |  Base Class   |    Trait    |
                      ===============================================
> 1 per class         |      Yes      |       No      |     Yes     |
---------------------------------------------------------------------
Define Method Body    |      No       |       Yes     |     Yes     |
---------------------------------------------------------------------
Polymorphism          |      Yes      |       Yes     |     No      |
---------------------------------------------------------------------

Polimorfismo:

No exemplo anterior, onde MyClass se estende SomeBaseClass , MyClass é uma instância de SomeBaseClass. Em outras palavras, uma matriz como SomeBaseClass[] basespode conter instâncias de MyClass. Da mesma forma, se MyClassestendida IBaseInterface, uma matriz de IBaseInterface[] basespoderia conter instâncias de MyClass. Não existe essa construção polimórfica disponível com a trait- porque a traité essencialmente apenas um código que é copiado para conveniência do programador em cada classe que a utiliza.

Precedência:

Conforme descrito no manual:

Um membro herdado de uma classe base é substituído por um membro inserido por um Trait. A ordem de precedência é que os membros da classe atual substituam os métodos Trait, que, em troca, substituem os métodos herdados.

Portanto, considere o seguinte cenário:

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 */ }
}

Ao criar uma instância do MyClass, acima, ocorre o seguinte:

  1. O Interface IBaserequer uma função sem parâmetros chamada SomeMethod()a ser fornecida.
  2. A classe base BaseClassfornece uma implementação desse método - satisfazendo a necessidade.
  3. O trait myTraitfornece uma função sem parâmetros chamada SomeMethod()também, que tem precedência sobre a BaseClassversão
  4. O class MyClassfornece sua própria versão de SomeMethod()- que tem precedência sobre a traitversão -.

Conclusão

  1. Um Interfacenão pode fornecer uma implementação padrão de um corpo de método, enquanto um traitpode.
  2. Um Interfaceé um polimórfica , herdada construo - enquanto um traitnão é.
  3. Múltiplos Interfaces podem ser usados ​​na mesma classe, assim como múltiplos traits.
Troy Alford
fonte
4
"Uma característica é semelhante ao conceito C # de uma classe abstrata" Não, uma classe abstrata é uma classe abstrata; esse conceito existe no PHP e no C #. Em vez disso, compararia uma característica no PHP com uma classe estática feita de métodos de extensão em C #, com a restrição baseada em tipo removida, pois ela pode ser usada por praticamente qualquer tipo, diferente de um método de extensão que apenas estende um tipo.
BoltClock
11
Muito bom comentário - e eu concordo com você. Na releitura, essa é uma analogia melhor. Acredito que ainda é melhor pensar nisso como um mixin- e, ao revisitar a abertura da minha resposta, atualizei para refletir isso. Obrigado por comentar, @BoltClock!
Troy Alford
11
Eu não acho que exista qualquer relação com os métodos de extensão c #. Os métodos de extensão são adicionados ao tipo de classe única (respeitando a hierarquia de classes, é claro). Seu objetivo é aprimorar um tipo com funcionalidade adicional, não "compartilhar código" em várias classes e fazer uma bagunça. Não é comparável! Se algo precisar ser reutilizado, geralmente significa que ele deve ter espaço próprio, como uma classe separada, que estaria relacionada às classes que precisam de funcionalidade comum. A implementação pode variar dependendo do design, mas é basicamente isso. Os traços são apenas outra maneira de criar um código ruim.
Sofija
Uma classe pode ter várias interfaces? Não tenho certeza se estou enganando seu gráfico, mas a classe X implementa Y, Z é válido.
Yann Chabot
26

Eu acho que traitsé útil criar classes que contêm métodos que podem ser usados ​​como métodos de várias classes diferentes.

Por exemplo:

trait ToolKit
{
    public $errors = array();

    public function error($msg)
    {
        $this->errors[] = $msg;
        return false;
    }
}

Você pode ter e usar esse método de "erro" em qualquer classe que use essa característica.

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
    }
}

Enquanto interfacesvocê só pode declarar a assinatura do método, mas não o código de suas funções. Além disso, para usar uma interface, você precisa seguir uma hierarquia, usando implements. Este não é o caso de características.

É completamente diferente!

J. Bruni
fonte
Eu acho que este é um mau exemplo de uma característica. to_integerprovavelmente seria incluído em uma IntegerCastinterface porque não há uma maneira fundamentalmente semelhante de converter (inteligentemente) classes para um número inteiro.
Matthew
5
Esqueça "to_integer" - é apenas uma ilustração. Um exemplo. Um "Olá, mundo". Um "exemplo.com".
193 J. Bruni
2
Que benefício esse traço de kit de ferramentas fornece que uma classe de utilitário independente não poderia? Em vez de use Toolkitvocê poder ter $this->toolkit = new Toolkit();ou estou perdendo algum benefício da característica em si?
Anthony
Em algum lugar @Anthony em sua Something's recipiente que você fazif(!$something->do_something('foo')) var_dump($something->errors);
TheRealChx101
20

Para os iniciantes acima da resposta, pode ser difícil, esta é a maneira mais fácil de entender:

Traits

trait SayWorld {
    public function sayHello() {
        echo 'World!';
    }
}

portanto, se você deseja ter sayHellofunção em outras classes sem recriar toda a função, pode usar traços,

class MyClass{
  use SayWorld;

}

$o = new MyClass();
$o->sayHello();

Legal certo!

Não apenas funções, você pode usar qualquer coisa na característica (função, variáveis, const ..). Além disso, você pode usar várias características:use SayWorld,AnotherTraits;

Interface

  interface SayWorld {
     public function sayHello();
  }

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

portanto, é assim que a interface é diferente das características: você precisa recriar tudo na interface na classe implementada. interface não tem implementação. e interface só pode ter funções e const, não pode ter variáveis.

Eu espero que isso ajude!

Supun Praneeth
fonte
5

Uma metáfora frequentemente usada para descrever os traços é que os traços são interfaces com a implementação.

Essa é uma boa maneira de pensar sobre isso na maioria das circunstâncias, mas há várias diferenças sutis entre as duas.

Para começar, o instanceofoperador não trabalhará com características (ou seja, uma característica não é um objeto real), portanto você não pode ver se uma classe tem uma certa característica (ou se duas classes não relacionadas compartilham uma característica ) É isso que eles querem dizer com uma construção para reutilização de código horizontal.

Agora, existem funções no PHP que permitem obter uma lista de todas as características que uma classe usa, mas a herança de características significa que você precisará fazer verificações recursivas para verificar com segurança se uma classe em algum momento tem uma característica específica (há exemplo código nas páginas de documentação do PHP). Mas sim, certamente não é tão simples e limpo como o instanceof, e IMHO é um recurso que tornaria o PHP melhor.

Além disso, as classes abstratas ainda são classes, portanto, não resolvem problemas de reutilização de códigos relacionados à herança múltipla. Lembre-se de que você só pode estender uma classe (real ou abstrata), mas implementar várias interfaces.

Eu descobri que características e interfaces são realmente boas de usar em conjunto para criar herança pseudo-múltipla. Por exemplo:

class SlidingDoor extends Door implements IKeyed  
{  
    use KeyedTrait;  
    [...] // Generally not a lot else goes here since it's all in the trait  
}

Fazer isso significa que você pode usar instanceof para determinar se o objeto Door específico é Keyed ou não, você sabe que obterá um conjunto consistente de métodos etc., e todo o código está em um único lugar em todas as classes que usam o KeyedTrait.

Jon Kloske
fonte
A última parte dessa resposta é, obviamente, o que @rdlowrey está dizendo com mais detalhes nos últimos três parágrafos em "Traits" em seu post; Eu apenas senti que um trecho de código esqueleto realmente simples ajudaria a ilustrá-lo.
precisa saber é o seguinte
Eu acho que a melhor maneira OO de usar características é usando interfaces onde você pode. E se há um caso quando várias subclasses implementar o mesmo tipo de código para essa interface e você não pode mover esse código para sua superclasse (resumo) -> implementá-lo com traços
jogador e um
4

Os traços são simplesmente para reutilização de código .

A interface apenas fornece a assinatura das funções que devem ser definidas na classe em que podem ser usadas, dependendo da discrição do programador . Dando assim um protótipo para um grupo de classes .

Para referência - http://www.php.net/manual/en/language.oop5.traits.php

Rajesh Paul
fonte
3

Você pode considerar uma característica como uma "copiar e colar" automatizada de código, basicamente.

O uso de características é perigoso, pois não há como saber o que ele faz antes da execução.

No entanto, os traços são mais flexíveis devido à falta de limitações, como herança.

As características podem ser úteis para injetar um método que verifica algo em uma classe, por exemplo, a existência de outro método ou atributo. Um bom artigo sobre isso (mas em francês, desculpe) .

Para as pessoas que conseguem ler em francês, a Revista GNU / Linux HS 54 tem um artigo sobre esse assunto.

Benj
fonte
Ainda não entendo como características são diferentes das interfaces com implementação padrão
denis631
@ denis631 Pode ver Traits como fragmentos de código e interfaces como contratos de assinatura. Se isso puder ajudar, você pode vê-lo como uma parte informal de uma classe que pode conter qualquer coisa. Deixe-me saber se isso ajuda.
21418 Benj
Vejo que os traços do PHP podem ser vistos como macros que são expandidas em tempo de compilação / apenas com o apelido de trecho de código com essa chave. Os traços de ferrugem, no entanto, parecem diferentes (ou estou errado). Mas como os dois têm traços de palavras, eu assumiria que eles são os mesmos, significando o mesmo conceito. Link de características de ferrugem: doc.rust-lang.org/rust-by-example/trait.html
denis631
2

Se você sabe inglês e sabe o que traitsignifica, é exatamente o que o nome diz. É um pacote de métodos e propriedades sem classe que você anexa a classes existentes, digitando use.

Basicamente, você pode compará-lo a uma única variável. As funções de fechamento podem useessas variáveis ​​de fora do escopo e dessa maneira elas têm o valor dentro. Eles são poderosos e podem ser usados ​​em tudo. O mesmo acontece com as características se elas estiverem sendo usadas.

Thielicious
fonte
2

Outras respostas fizeram um ótimo trabalho ao explicar as diferenças entre interfaces e características. Vou me concentrar em um exemplo útil do mundo real, em particular um que demonstra que os traços podem usar variáveis ​​de instância - permitindo adicionar comportamento a uma classe com o mínimo de código padrão.

Novamente, como mencionado por outros, os traços combinam bem com as interfaces, permitindo que a interface especifique o contrato de comportamento e a característica para cumprir a implementação.

Adicionar recursos de publicação / assinatura de eventos a uma classe pode ser um cenário comum em algumas bases de código. Existem 3 soluções comuns:

  1. Defina uma classe base com o código de pub / sub de eventos e, em seguida, as classes que desejam oferecer eventos podem estendê-la para obter os recursos.
  2. Defina uma classe com o código pub / sub do evento e, em seguida, outras classes que desejam oferecer eventos podem usá-la por meio da composição, definindo seus próprios métodos para quebrar o objeto composto, fazendo proxy das chamadas de método para ele.
  3. Defina uma característica com o código de pub / sub evento e, em seguida, outras classes que desejam oferecer eventos podem usea característica, também conhecida como importá-la, para obter os recursos.

Quão bem cada um funciona?

# 1 não funciona bem. Até o dia em que você perceber que não pode estender a classe base, porque já está estendendo outra coisa. Não mostrarei um exemplo disso, porque deve ser óbvio o quão limitado é usar herança como esta.

# 2 e # 3 funcionam bem. Vou mostrar um exemplo que destaca algumas diferenças.

Primeiro, algum código que será o mesmo entre os dois exemplos:

Uma interface

interface Observable {
    function addEventListener($eventName, callable $listener);
    function removeEventListener($eventName, callable $listener);
    function removeAllEventListeners($eventName);
}

E algum código para demonstrar o uso:

$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());
}

Ok, agora vamos mostrar como a implementação da Auctionclasse será diferente ao usar traços.

Primeiro, veja como seria o número 2 (usando composição):

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);
    }
}

Veja como o número 3 (traços) seria:

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]);
    }
}

Observe que o código dentro da EventEmitterTraité exatamente o mesmo que está dentro da EventEmitterclasse, exceto que a característica declara o triggerEvent()método como protegido. Portanto, a única diferença que você precisa observar é a implementação da Auctionclasse .

E a diferença é grande. Ao usar a composição, obtemos uma ótima solução, permitindo que reutilizemos o nosso EventEmitterpor quantas classes quisermos. Mas, a principal desvantagem é que temos muito código clichê que precisamos escrever e manter, pois para cada método definido na Observableinterface, precisamos implementá-lo e escrever código clichê chato que apenas encaminha os argumentos para o método correspondente em nosso compôs o EventEmitterobjeto. O uso da característica neste exemplo nos permite evitar isso , ajudando-nos a reduzir o código padrão e melhorar a capacidade de manutenção .

No entanto, pode haver momentos em que você não deseja que sua Auctionclasse implemente a Observableinterface completa - talvez você queira apenas expor 1 ou 2 métodos, ou talvez nenhum, para que você possa definir suas próprias assinaturas de método. Nesse caso, você ainda pode preferir o método de composição.

Mas, a característica é muito atraente na maioria dos cenários, especialmente se a interface tiver muitos métodos, o que faz com que você escreva muitos clichês.

* Você pode realmente fazer as duas coisas - defina a EventEmitterclasse caso queira usá-la em termos de composição e defina a EventEmitterTraitcaracterística também, usando a EventEmitterimplementação da classe dentro da característica :)

bode
fonte
1

A característica é a mesma que uma classe que podemos usar para vários propósitos de herança e também reutilização de código.

Podemos usar características dentro da classe e também podemos usar várias características na mesma classe com 'use keyword'.

A interface está usando para reutilização de código o mesmo que uma característica

a interface é estender várias interfaces para que possamos resolver os problemas de herança múltipla, mas quando implementamos a interface, devemos criar todos os métodos dentro da classe. Para mais informações, clique no link abaixo:

http://php.net/manual/en/language.oop5.traits.php http://php.net/manual/en/language.oop5.interfaces.php

Chirag Prajapati
fonte
1

Uma interface é um contrato que diz "esse objeto é capaz de fazer isso", enquanto uma característica está dando ao objeto a capacidade de fazer a coisa.

Uma característica é essencialmente uma maneira de "copiar e colar" o código entre as classes.

Tente ler este artigo, Quais são as características do PHP?

Hos Mercury
fonte
0

A principal diferença é que, com interfaces, você deve definir a implementação real de cada método dentro de cada classe que implementa a referida interface, para que muitas classes implementem a mesma interface, mas com comportamento diferente, enquanto traços são apenas pedaços de código injetado em uma aula; outra diferença importante é que os métodos de características podem ser apenas métodos de classe ou métodos estáticos, diferentemente dos métodos de interface que também podem (e geralmente são) ser métodos de instância.

Alessandro Martin
fonte