Rasgos versus interfaces

344

Últimamente he estado tratando de estudiar PHP y me encuentro colgado de los rasgos. Entiendo el concepto de reutilización de código horizontal y no querer heredar necesariamente de una clase abstracta. Lo que no entiendo es: ¿Cuál es la diferencia crucial entre usar rasgos versus interfaces?

He intentado buscar una publicación de blog o un artículo decente que explique cuándo usar uno u otro, pero los ejemplos que he encontrado hasta ahora parecen tan similares como idénticos.

datguywhowanders
fuente
66
La interfaz no tiene ningún código en los cuerpos de función. en realidad no tienen ningún cuerpo funcional.
Hakre
2
A pesar de mi respuesta muy votada, me gustaría que constara para el registro que generalmente soy anti-rasgo / mixin . Consulte esta transcripción del chat para leer cómo los rasgos a menudo socavan las prácticas sólidas de OOP .
rdlowrey
2
Yo argumentaría lo contrario. Después de haber trabajado con PHP durante años y desde la aparición de los rasgos, creo que es fácil demostrar su valía. Simplemente lea este ejemplo práctico que permite que los 'modelos de imagen' también caminen y hablen como Imagickobjetos, menos toda la hinchazón necesaria en los viejos tiempos antes de los rasgos.
quickshiftin
Los rasgos y la interfaz son similares. La principal diferencia es que los Rasgos le permiten implementar los métodos, la interfaz no.
John

Respuestas:

238

Una interfaz define un conjunto de métodos que la clase implementadora debe implementar.

Cuando un rasgo es used, las implementaciones de los métodos también aparecen, lo que no sucede en un Interface.

Esa es la mayor diferencia.

Desde la reutilización horizontal para PHP RFC :

Rasgos es un mecanismo para la reutilización de código en lenguajes de herencia única como PHP. Un rasgo está destinado a reducir algunas limitaciones de la herencia única al permitir que un desarrollador reutilice conjuntos de métodos libremente en varias clases independientes que viven en diferentes jerarquías de clases.

Garganta de Alec
fuente
2
@JREAM En la práctica, nada. En realidad, mucho más.
Alec Gorge
79
Excepto que los rasgos no son interfaces en absoluto. Las interfaces son especificaciones que se pueden verificar. Los rasgos no se pueden verificar, por lo tanto, son solo de implementación. Son exactamente lo contrario de las interfaces. Esa línea en el RFC simplemente está mal ...
ircmaxell
195
Los rasgos son esencialmente copiar y pegar con ayuda de idioma .
Shahid
10
Eso no es una metáfora. Eso es descifrar el significado de una palabra. Es como describir una caja como una superficie con volumen.
cleong
66
Para ampliar los comentarios de ircmaxell y Shadi: puede verificar si un objeto implementa una interfaz (a través de instanceof), y puede asegurarse de que un argumento de método implemente una interfaz a través de una pista de tipo en la firma del método. No puede realizar las comprobaciones correspondientes para los rasgos.
Brian D'Astous
530

Anuncio de servicio publico:

Quiero dejar constancia de que creo que los rasgos son casi siempre un olor a código y deben evitarse en favor de la composición. Es mi opinión que la herencia única se abusa con frecuencia hasta el punto de ser un antipatrón y la herencia múltiple solo agrava este problema. Será mucho mejor servido en la mayoría de los casos al favorecer la composición sobre la herencia (ya sea individual o múltiple). Si todavía está interesado en los rasgos y su relación con las interfaces, siga leyendo ...


Comencemos diciendo esto:

La programación orientada a objetos (OOP) puede ser un paradigma difícil de comprender. El hecho de que esté usando clases no significa que su código esté orientado a objetos (OO).

Para escribir código OO necesita comprender que OOP realmente se trata de las capacidades de sus objetos. Tienes que pensar en las clases en términos de lo que pueden hacer en lugar de lo que realmente hacen . Esto está en marcado contraste con la programación tradicional de procedimientos donde el foco está en hacer que un poco de código "haga algo".

Si el código OOP se trata de planificación y diseño, una interfaz es el plano y un objeto es la casa totalmente construida. Mientras tanto, los rasgos son simplemente una forma de ayudar a construir la casa establecida por el plan (la interfaz).

Interfaces

Entonces, ¿por qué deberíamos usar interfaces? En pocas palabras, las interfaces hacen que nuestro código sea menos frágil. Si duda de esta afirmación, pregúntele a cualquiera que se haya visto obligado a mantener el código heredado que no estaba escrito en las interfaces.

La interfaz es un contrato entre el programador y su código. La interfaz dice: "Siempre que cumpla con mis reglas, puede implementarme como quiera y le prometo que no romperé su otro código".

Entonces, como ejemplo, considere un escenario del mundo real (sin automóviles ni widgets):

Desea implementar un sistema de almacenamiento en caché para una aplicación web para reducir la carga del servidor

Comienza escribiendo una clase para almacenar en caché las respuestas de solicitud utilizando 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);
  }
}

Luego, en su objeto de respuesta HTTP, verifica si hay un acierto de caché antes de hacer todo el trabajo para generar la respuesta 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;
  }
}

Este enfoque funciona muy bien. Pero tal vez unas semanas después decida que desea usar un sistema de caché basado en archivos en lugar de APC. Ahora tiene que cambiar su código de controlador porque ha programado su controlador para que funcione con la funcionalidad de la ApcCacherclase en lugar de una interfaz que exprese las capacidades de la ApcCacherclase. Digamos que en lugar de lo anterior, hizo que la Controllerclase dependiera de un CacherInterfacelugar en lugar del concreto de esta ApcCachermanera:

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

Para continuar, define su interfaz de esta manera:

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

A su vez, tanto usted ApcCachercomo sus nuevas FileCacherclases implementan CacherInterfacey programa su Controllerclase para usar las capacidades requeridas por la interfaz.

Este ejemplo (con suerte) demuestra cómo la programación de una interfaz le permite cambiar la implementación interna de sus clases sin preocuparse si los cambios romperán su otro código.

Rasgos

Los rasgos, por otro lado, son simplemente un método para reutilizar el código. Las interfaces no deben considerarse como una alternativa mutuamente excluyente a los rasgos. De hecho, crear rasgos que cumplan con las capacidades requeridas por una interfaz es el caso de uso ideal .

Solo debe usar rasgos cuando varias clases comparten la misma funcionalidad (probablemente dictada por la misma interfaz). No tiene sentido usar un rasgo para proporcionar funcionalidad para una sola clase: eso solo ofusca lo que hace la clase y un mejor diseño movería la funcionalidad del rasgo a la clase relevante.

Considere la siguiente implementación de rasgos:

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

Un ejemplo más concreto: imagine que tanto usted FileCachercomo su parte ApcCacherde la discusión de la interfaz utilizan el mismo método para determinar si una entrada de caché está obsoleta y debe eliminarse (obviamente, este no es el caso en la vida real, pero vaya con ella). Podría escribir un rasgo y permitir que ambas clases lo usen para el requisito de interfaz común.

Una última palabra de precaución: tenga cuidado de no exagerar con los rasgos. A menudo, los rasgos se usan como una muleta para un diseño deficiente cuando bastan implementaciones de clase únicas. Debe limitar los rasgos para cumplir con los requisitos de interfaz para el mejor diseño de código.

rdlowrey
fuente
69
Realmente estaba buscando la respuesta rápida y simple que se proporcionó anteriormente, pero tengo que decir que dio una excelente respuesta en profundidad que ayudará a aclarar la distinción para los demás, felicitaciones.
datguywhowanders
35
"Los rasgos de clasificación [C] que cumplen con las capacidades requeridas por una interfaz en una clase dada es un caso de uso ideal". Exactamente: +1
Alec Gorge
55
¿Sería justo decir que los rasgos en PHP son similares a los mixins en otros idiomas?
Eno
55
@igorpan Para todos los efectos, diría que la implementación del rasgo de PHP es lo mismo que la herencia múltiple. Vale la pena señalar que si un rasgo en PHP especifica propiedades estáticas, entonces cada clase que use el rasgo tendrá su propia copia de la propiedad estática. Más importante aún ... al ver cómo esta publicación ahora es extremadamente alta en los SERPs cuando se consultan los rasgos, voy a agregar un anuncio de servicio público en la parte superior de la página. Deberías leerlo.
rdlowrey 01 de
3
+1 para una explicación en profundidad. Vengo de un fondo rubí, donde los mixins se usan MUCHO; solo para agregar mis dos centavos, una buena regla general que usamos podría traducirse en php como "no implemente métodos que muten $ this en rasgos". Esto evita un montón de locas sesiones de depuración ... Un mixin tampoco debe hacer suposiciones sobre la clase en la que se mezclará (o debe dejarlo muy claro y reducir las dependencias al mínimo). En este sentido, me parece agradable su idea de rasgos que implementan interfaces.
m_x
67

A traites esencialmente la implementación de PHP de a mixin, y es efectivamente un conjunto de métodos de extensión que se pueden agregar a cualquier clase mediante la adición de trait. Los métodos luego se convierten en parte de la implementación de esa clase, pero sin usar la herencia .

Del Manual PHP (énfasis mío):

Los rasgos son un mecanismo para la reutilización de código en lenguajes de herencia única como PHP. ... Es una adición a la herencia tradicional y permite la composición horizontal del comportamiento; es decir, la aplicación de los miembros de la clase sin requerir herencia.

Un ejemplo:

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

Con el rasgo anterior definido, ahora puedo hacer lo siguiente:

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

En este punto, cuando creo una instancia de clase MyClass, tiene dos métodos, llamados foo()y bar(), que provienen de myTrait. Y, observe que los traitmétodos definidos ya tienen un cuerpo de método, que un Interfacemétodo definido no puede.

Además, PHP, como muchos otros lenguajes, usa un solo modelo de herencia , lo que significa que una clase puede derivar de múltiples interfaces, pero no de múltiples clases. Sin embargo, una clase PHP puede tener múltiples traitinclusiones, lo que le permite al programador incluir piezas reutilizables, como podrían incluir varias clases base.

Algunas cosas a tener en cuenta:

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

Polimorfismo:

En el ejemplo anterior, donde se MyClass extiende SomeBaseClass , MyClass es una instancia de SomeBaseClass. En otras palabras, una matriz como SomeBaseClass[] basespuede contener instancias de MyClass. Del mismo modo, si se MyClassextiende IBaseInterface, una matriz de IBaseInterface[] basespodría contener instancias de MyClass. No hay tal construcción polimórfica disponible con un trait- porque a traites esencialmente un código que se copia para la conveniencia del programador en cada clase que lo usa.

Precedencia:

Como se describe en el Manual:

Un miembro heredado de una clase base es anulado por un miembro insertado por un Rasgo. El orden de precedencia es que los miembros de la clase actual anulan los métodos de Rasgo, que a cambio anulan los métodos heredados.

Entonces, considere el siguiente escenario:

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

Al crear una instancia de MyClass, arriba, ocurre lo siguiente:

  1. El Interface IBaserequiere una función sin parámetros llamada SomeMethod()para ser proporcionada.
  2. La clase base BaseClassproporciona una implementación de este método, satisfaciendo la necesidad.
  3. También trait myTraitproporciona una función sin parámetros llamada SomeMethod(), que tiene prioridad sobre la BaseClassversión
  4. El class MyClassproporciona su propia versión de SomeMethod(), que tiene prioridad sobre la traitversión.

Conclusión

  1. Un Interfaceno puede proporcionar una implementación predeterminada de un cuerpo de método, mientras que un traitpuede.
  2. Una Interfacees un polimórfica , heredado construct - mientras que una traitno lo es.
  3. Se Interfacepueden usar múltiples s en la misma clase, y también se pueden usar múltiples traits.
Troy Alford
fuente
44
"Un rasgo es similar al concepto C # de una clase abstracta" No, una clase abstracta es una clase abstracta; ese concepto existe tanto en PHP como en C #. En cambio, compararía un rasgo en PHP con una clase estática hecha de métodos de extensión en C #, con la restricción basada en el tipo eliminada ya que un rasgo puede ser utilizado por casi cualquier tipo, a diferencia de un método de extensión que solo extiende un tipo.
BoltClock
1
Muy buen comentario, y estoy de acuerdo contigo. En relectura, esa es una mejor analogía. Sin embargo, creo que es mejor pensar en ello como un mixin- y al volver a visitar la apertura de mi respuesta, me actualicé para reflejar esto. Gracias por comentar, @BoltClock!
Troy Alford
1
No creo que haya ninguna relación con los métodos de extensión de C #. Los métodos de extensión se agregan al tipo de clase única (respetando la jerarquía de clases, por supuesto), su propósito es mejorar un tipo con funcionalidad adicional, no "compartir código" en varias clases y hacer un desastre. ¡No es comparable! Si algo necesita ser reutilizado, generalmente significa que debe tener espacio propio, como una clase separada que estaría relacionada con las clases que necesitan una funcionalidad común. La implementación puede variar según el diseño, pero más o menos eso es todo. Los rasgos son solo otra forma de hacer un código pobre.
Sofija
¿Una clase puede tener múltiples interfaces? No estoy seguro de si me equivoco con el gráfico, pero la clase X implementa Y, Z es válido.
Yann Chabot
26

Creo que traitsson útiles para crear clases que contienen métodos que pueden usarse como métodos de varias clases diferentes.

Por ejemplo:

trait ToolKit
{
    public $errors = array();

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

Puede tener y usar este método de "error" en cualquier clase que use este rasgo.

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

Mientras que con interfacesusted solo puede declarar la firma del método, pero no el código de sus funciones. Además, para usar una interfaz, debe seguir una jerarquía, usando implements. Este no es el caso con los rasgos.

¡Es completamente diferente!

J. Bruni
fuente
Creo que este es un mal ejemplo de un rasgo. to_integerprobablemente se incluiría en una IntegerCastinterfaz porque no existe una forma fundamentalmente similar de (inteligentemente) transmitir clases a un entero.
Mateo
55
Olvídate de "to_integer", es solo una ilustración. Un ejemplo. Un "Hola, mundo". Un "ejemplo.com".
J. Bruni
2
¿Qué beneficio proporciona este rasgo del kit de herramientas que una clase de utilidad independiente no podría? ¿En lugar de use Toolkitusted podría tener $this->toolkit = new Toolkit();o me estoy perdiendo algún beneficio del rasgo en sí?
Anthony
@Anthony en algún lugar de tu Somethingcontenedor que hacesif(!$something->do_something('foo')) var_dump($something->errors);
TheRealChx101
20

Para los principiantes, la respuesta anterior puede ser difícil. Esta es la forma más fácil de entenderlo:

Rasgos

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

así que si quieres tener una sayHellofunción en otras clases sin volver a crear la función completa, puedes usar rasgos,

class MyClass{
  use SayWorld;

}

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

¡Guay, verdad!

No solo las funciones pueden usar cualquier cosa en el rasgo (función, variables, const ..). También puedes usar múltiples rasgos:use SayWorld,AnotherTraits;

Interfaz

  interface SayWorld {
     public function sayHello();
  }

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

así es como la interfaz es diferente de los rasgos: debe volver a crear todo en la interfaz en la clase implementada. La interfaz no tiene implementación. y la interfaz solo puede tener funciones y constantes, no puede tener variables.

¡Espero que esto ayude!

Supun Praneeth
fuente
5

Una metáfora de uso frecuente para describir los Rasgos es que los Rasgos son interfaces con implementación.

Esta es una buena manera de pensarlo en la mayoría de las circunstancias, pero hay una serie de diferencias sutiles entre los dos.

Para empezar, el instanceofoperador no trabajará con rasgos (es decir, un rasgo no es un objeto real), por lo que no puede usarlo para ver si una clase tiene un rasgo determinado (o para ver si dos clases no relacionadas comparten un rasgo) ) Eso es lo que quieren decir con que es una construcción para la reutilización de código horizontal.

Ahora hay funciones en PHP que le permitirán obtener una lista de todos los rasgos que usa una clase, pero la herencia de rasgos significa que tendrá que hacer verificaciones recursivas para verificar de manera confiable si una clase en algún momento tiene un rasgo específico (hay un ejemplo código en las páginas de PHP doco). Pero sí, ciertamente no es tan simple y limpio como la instancia de lo es, y en mi humilde opinión, es una característica que mejoraría PHP.

Además, las clases abstractas siguen siendo clases, por lo que no resuelven problemas de reutilización de código relacionados con la herencia múltiple. Recuerde que solo puede extender una clase (real o abstracta) pero implementar múltiples interfaces.

He descubierto que los rasgos y las interfaces son realmente buenos para usar de la mano para crear una herencia pseudo múltiple. P.ej:

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

Hacer esto significa que puede usar instanceof para determinar si el objeto de la Puerta en particular está Keyed o no, sabe que obtendrá un conjunto consistente de métodos, etc., y todo el código está en un lugar en todas las clases que usan KeyedTrait.

Jon Kloske
fuente
La última parte de esa respuesta es, por supuesto, lo que @rdlowrey dice con más detalle en los últimos tres párrafos bajo "Rasgos" en su publicación; Simplemente sentí que un fragmento de código esqueleto realmente simple ayudaría a ilustrarlo.
Jon Kloske
Creo que la mejor manera OO de usar rasgos es usar interfaces donde puedas. Y si hay un caso cuando varias subclases implementan el mismo tipo de código para esa interfaz y no puede mover ese código a su superclase (abstracta) -> implementarlo con rasgos
jugador uno el
4

Los rasgos son simplemente para reutilizar el código .

La interfaz solo proporciona la firma de las funciones que se definirán en la clase donde se puede utilizar según el criterio del programador . Así nos da un prototipo para un grupo de clases .

Para referencia: http://www.php.net/manual/en/language.oop5.traits.php

Rajesh Paul
fuente
3

Puedes considerar un rasgo como un "copiar y pegar" automatizado de código, básicamente.

Usar rasgos es peligroso ya que no hay forma de saber qué hace antes de la ejecución.

Sin embargo, los rasgos son más flexibles debido a su falta de limitaciones, como la herencia.

Los rasgos pueden ser útiles para inyectar un método que verifica algo en una clase, por ejemplo, la existencia de otro método o atributo. Un buen artículo sobre eso (pero en francés, lo siento) .

Para las personas que leen en francés que pueden obtenerlo, la revista GNU / Linux Magazine HS 54 tiene un artículo sobre este tema.

Benj
fuente
Todavía no entiendo cómo los rasgos son diferentes de las interfaces con implementación predeterminada
denis631
@ denis631 Puede ver los Rasgos como fragmentos de código y las interfaces como contratos de firma. Si puede ayudar, puede verlo como una parte informal de una clase que puede contener cualquier cosa. Avísame si te ayuda.
Benj
Veo que los rasgos de PHP se pueden ver como macros que luego se expanden en tiempo de compilación / simplemente alias ese fragmento de código con esa clave. Los rasgos de óxido, sin embargo, parecen diferentes (o me equivoco). Pero dado que ambos tienen rasgos de palabras, supongo que son lo mismo, es decir, el mismo concepto. Enlace de rasgos de óxido: doc.rust-lang.org/rust-by-example/trait.html
denis631
2

Si sabes inglés y sabes lo que traitsignifica, es exactamente lo que dice el nombre. Es un paquete de métodos y propiedades sin clase que se adjunta a las clases existentes escribiendo use.

Básicamente, podría compararlo con una sola variable. Las funciones de cierre pueden useestas variables desde fuera del alcance y de esa manera tienen el valor dentro. Son poderosos y se pueden usar en todo. Lo mismo sucede con los rasgos si se están utilizando.

Thielicious
fuente
2

Otras respuestas hicieron un gran trabajo al explicar las diferencias entre interfaces y rasgos. Me centraré en un ejemplo útil del mundo real, en particular uno que demuestre que los rasgos pueden usar variables de instancia, lo que le permite agregar comportamiento a una clase con un código mínimo repetitivo.

Nuevamente, como lo mencionaron otros, los rasgos se combinan bien con las interfaces, permitiendo que la interfaz especifique el contrato de comportamiento y el rasgo para cumplir con la implementación.

Agregar capacidades de publicación / suscripción de eventos a una clase puede ser un escenario común en algunas bases de código. Hay 3 soluciones comunes:

  1. Defina una clase base con código de publicación / sub de eventos, y luego las clases que desean ofrecer eventos pueden extenderla para obtener las capacidades.
  2. Defina una clase con el código de publicación / sub del evento, y luego otras clases que deseen ofrecer eventos pueden usarla a través de la composición, definiendo sus propios métodos para envolver el objeto compuesto, representando las llamadas al método.
  3. Defina un rasgo con el código de pub / sub del evento, y luego otras clases que deseen ofrecer eventos pueden useel rasgo, también conocido como importarlo, para obtener las capacidades.

¿Qué tan bien funciona cada uno?

# 1 no funciona bien. Lo haría, hasta el día en que te des cuenta de que no puedes extender la clase base porque ya estás extendiendo algo más. No mostraré un ejemplo de esto porque debería ser obvio cuán limitante es usar una herencia como esta.

# 2 y # 3 ambos funcionan bien. Mostraré un ejemplo que resalta algunas diferencias.

Primero, un código que será el mismo entre ambos ejemplos:

Una interfaz

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

Y algo de código para demostrar el 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, ahora vamos a mostrar cómo la implementación de la Auction clase diferirá al usar rasgos.

Primero, así es como se vería el # 2 (usando composición):

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

Así es como se vería el # 3 (rasgos):

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

Tenga en cuenta que el código dentro de EventEmitterTraites exactamente el mismo que está dentro de la EventEmitterclase, excepto que el rasgo declara que el triggerEvent()método está protegido. Entonces, la única diferencia que debe observar es la implementación de la Auctionclase .

Y la diferencia es grande. Cuando usamos composición, obtenemos una gran solución, que nos permite reutilizar nuestras EventEmitterclases tantas veces como queramos. Pero, el principal inconveniente es que tenemos una gran cantidad de código repetitivo que necesitamos escribir y mantener porque para cada método definido en la Observableinterfaz, necesitamos implementarlo y escribir código repetitivo aburrido que solo reenvíe los argumentos al método correspondiente en nuestro compuesto el EventEmitterobjeto. El uso del rasgo en este ejemplo nos permite evitar eso , lo que nos ayuda a reducir el código repetitivo y mejorar la capacidad de mantenimiento .

Sin embargo, puede haber ocasiones en las que no desee que su Auctionclase implemente la versión completaObservable interfaz ; tal vez solo quiera exponer 1 o 2 métodos, o tal vez incluso ninguno para que pueda definir sus propias firmas de métodos. En tal caso, aún puede preferir el método de composición.

Pero, el rasgo es muy convincente en la mayoría de los escenarios, especialmente si la interfaz tiene muchos métodos, lo que hace que escribas muchas repeticiones.

* Realmente podrías hacer ambas cosas: define la EventEmitterclase en caso de que alguna vez quieras usarla de forma compositiva y define el EventEmitterTraitrasgo también, usando la EventEmitterimplementación de la clase dentro del rasgo :)

cabra
fuente
1

El rasgo es el mismo que una clase que podemos usar para múltiples propósitos de herencia y también para la reutilización de código.

Podemos usar rasgos dentro de la clase y también podemos usar múltiples rasgos en la misma clase con 'usar palabra clave'.

La interfaz está utilizando para la reutilización del código igual que un rasgo

la interfaz es extender múltiples interfaces para que podamos resolver los problemas de herencia múltiple, pero cuando implementamos la interfaz, debemos crear todos los métodos dentro de la clase. Para obtener más información, haga clic en el siguiente enlace:

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

Chirag Prajapati
fuente
0

La principal diferencia es que, con las interfaces, debe definir la implementación real de cada método dentro de cada clase que implementa dicha interfaz, por lo que puede hacer que muchas clases implementen la misma interfaz pero con un comportamiento diferente, mientras que los rasgos son solo fragmentos de código inyectados en una clase; Otra diferencia importante es que los métodos de rasgos solo pueden ser métodos de clase o métodos estáticos, a diferencia de los métodos de interfaz que también pueden (y generalmente son) métodos de instancia.

Alessandro Martin
fuente