Ú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.
Imagick
objetos, menos toda la hinchazón necesaria en los viejos tiempos antes de los rasgos.Respuestas:
Una interfaz define un conjunto de métodos que la clase implementadora debe implementar.
Cuando un rasgo es
use
d, las implementaciones de los métodos también aparecen, lo que no sucede en unInterface
.Esa es la mayor diferencia.
Desde la reutilización horizontal para PHP RFC :
fuente
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:
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):
Comienza escribiendo una clase para almacenar en caché las respuestas de solicitud utilizando APC:
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:
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
ApcCacher
clase en lugar de una interfaz que exprese las capacidades de laApcCacher
clase. Digamos que en lugar de lo anterior, hizo que laController
clase dependiera de unCacherInterface
lugar en lugar del concreto de estaApcCacher
manera:Para continuar, define su interfaz de esta manera:
A su vez, tanto usted
ApcCacher
como sus nuevasFileCacher
clases implementanCacherInterface
y programa suController
clase 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:
Un ejemplo más concreto: imagine que tanto usted
FileCacher
como su parteApcCacher
de 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.
fuente
A
trait
es esencialmente la implementación de PHP de amixin
, y es efectivamente un conjunto de métodos de extensión que se pueden agregar a cualquier clase mediante la adición detrait
. 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):
Un ejemplo:
Con el rasgo anterior definido, ahora puedo hacer lo siguiente:
En este punto, cuando creo una instancia de clase
MyClass
, tiene dos métodos, llamadosfoo()
ybar()
, que provienen demyTrait
. Y, observe que lostrait
métodos definidos ya tienen un cuerpo de método, que unInterface
mé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
trait
inclusiones, lo que le permite al programador incluir piezas reutilizables, como podrían incluir varias clases base.Algunas cosas a tener en cuenta:
Polimorfismo:
En el ejemplo anterior, donde se
MyClass
extiendeSomeBaseClass
,MyClass
es una instancia deSomeBaseClass
. En otras palabras, una matriz comoSomeBaseClass[] bases
puede contener instancias deMyClass
. Del mismo modo, si seMyClass
extiendeIBaseInterface
, una matriz deIBaseInterface[] bases
podría contener instancias deMyClass
. No hay tal construcción polimórfica disponible con untrait
- porque atrait
es 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:
Entonces, considere el siguiente escenario:
Al crear una instancia de MyClass, arriba, ocurre lo siguiente:
Interface
IBase
requiere una función sin parámetros llamadaSomeMethod()
para ser proporcionada.BaseClass
proporciona una implementación de este método, satisfaciendo la necesidad.trait
myTrait
proporciona una función sin parámetros llamadaSomeMethod()
, que tiene prioridad sobre laBaseClass
versiónclass
MyClass
proporciona su propia versión deSomeMethod()
, que tiene prioridad sobre latrait
versión.Conclusión
Interface
no puede proporcionar una implementación predeterminada de un cuerpo de método, mientras que untrait
puede.Interface
es un polimórfica , heredado construct - mientras que unatrait
no lo es.Interface
pueden usar múltiples s en la misma clase, y también se pueden usar múltiplestrait
s.fuente
mixin
- y al volver a visitar la apertura de mi respuesta, me actualicé para reflejar esto. Gracias por comentar, @BoltClock!Creo que
traits
son útiles para crear clases que contienen métodos que pueden usarse como métodos de varias clases diferentes.Por ejemplo:
Puede tener y usar este método de "error" en cualquier clase que use este rasgo.
Mientras que con
interfaces
usted 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, usandoimplements
. Este no es el caso con los rasgos.¡Es completamente diferente!
fuente
to_integer
probablemente se incluiría en unaIntegerCast
interfaz porque no existe una forma fundamentalmente similar de (inteligentemente) transmitir clases a un entero.use Toolkit
usted podría tener$this->toolkit = new Toolkit();
o me estoy perdiendo algún beneficio del rasgo en sí?Something
contenedor que hacesif(!$something->do_something('foo')) var_dump($something->errors);
Para los principiantes, la respuesta anterior puede ser difícil. Esta es la forma más fácil de entenderlo:
Rasgos
así que si quieres tener una
sayHello
función en otras clases sin volver a crear la función completa, puedes usar rasgos,¡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
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!
fuente
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
instanceof
operador 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:
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.
fuente
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
fuente
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.
fuente
Si sabes inglés y sabes lo que
trait
significa, es exactamente lo que dice el nombre. Es un paquete de métodos y propiedades sin clase que se adjunta a las clases existentes escribiendouse
.Básicamente, podría compararlo con una sola variable. Las funciones de cierre pueden
use
estas 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.fuente
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:
use
el 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
Y algo de código para demostrar el uso:
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):
Así es como se vería el # 3 (rasgos):
Tenga en cuenta que el código dentro de
EventEmitterTrait
es exactamente el mismo que está dentro de laEventEmitter
clase, excepto que el rasgo declara que eltriggerEvent()
método está protegido. Entonces, la única diferencia que debe observar es la implementación de laAuction
clase .Y la diferencia es grande. Cuando usamos composición, obtenemos una gran solución, que nos permite reutilizar nuestras
EventEmitter
clases 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 laObservable
interfaz, necesitamos implementarlo y escribir código repetitivo aburrido que solo reenvíe los argumentos al método correspondiente en nuestro compuesto elEventEmitter
objeto. 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
Auction
clase 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
EventEmitter
clase en caso de que alguna vez quieras usarla de forma compositiva y define elEventEmitterTrait
rasgo también, usando laEventEmitter
implementación de la clase dentro del rasgo :)fuente
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
fuente
Una interfaz es un contrato que dice "este objeto es capaz de hacer esto", mientras que un rasgo le da al objeto la capacidad de hacerlo.
Un rasgo es esencialmente una forma de "copiar y pegar" código entre clases.
Intenta leer este artículo, ¿Qué son los rasgos de PHP?
fuente
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.
fuente