¿Por qué PHP Trait no puede implementar interfaces?

82

Me pregunto por qué PHP Trait (PHP 5.4) no puede implementar interfaces.

Actualización de user1460043's answer => ... no puede requerir una clase que la use para implementar una interfaz específica

Entiendo que podría ser obvio, porque la gente podría pensar que si a Class Aestá usando a Trait Tque está implementando a interface I, entonces Class Adebería estar implementando el interface Iindirectamente (y esto no es cierto porque Class Apodría cambiar el nombre de los métodos de rasgos).

En mi caso, mi rasgo es llamar a métodos desde la interfaz que implementa la clase que usa el rasgo.

El rasgo es de hecho una implementación de algunos métodos de la interfaz. Entonces, quiero "diseñar" en el código que cada clase que quiera usar mi rasgo tenga que implementar la interfaz. Eso permitiría al Trait usar métodos de clase definidos por la interfaz y asegurarse de que existan en la clase.

Leto
fuente
13
Ese no es el punto, conozco la diferencia entre rasgos e interfaces.
Leto
1
Quizás haya una razón técnica, pero me pregunto por qué querrías hacerlo. No puede crear una instancia de un rasgo, por lo que implementar una interfaz no le brinda ningún beneficio de mecanografía. Si desea que esto, como dice, obligue a las clases que usan el rasgo a implementar una interfaz, entonces se preguntará si una clase base (abstracta) sería más adecuada.
Tiene razón, podría usar clases abstractas en todas partes, pero estoy actualizando mi código a Trait, y evito problemas que tuve con la herencia simple, por eso estoy usando trait. Entonces, tal vez en ese caso sea posible, pero en otros no lo es.
Leto
2
O tal vez en términos más simples: ¿por qué no hay tipos de rasgos en PHP?
nnevala

Respuestas:

97

La versión realmente corta es más simple porque no puedes. No es así como funcionan los rasgos.

Cuando escribe use SomeTrait;en PHP, le está diciendo (efectivamente) al compilador que copie y pegue el código del Trait en la clase donde se está utilizando.

Debido a que use SomeTrait;está dentro de la clase, no se puede agregar implements SomeInterfacea la clase, porque tiene que estar fuera de la clase.

"¿Por qué no hay tipos de rasgos en PHP?"

Porque no se pueden crear instancias. Los rasgos son en realidad solo una construcción del lenguaje (que le dice al compilador que copie y pegue el código del rasgo en esta clase) en lugar de un objeto o tipo al que su código puede hacer referencia.

Entonces, quiero "diseñar" en el código que cada clase que quiera usar mi rasgo tenga que implementar la interfaz.

Eso se puede hacer cumplir usando una clase abstracta para useel rasgo y luego extendiendo clases desde él.

interface SomeInterface{
    public function someInterfaceFunction();
}

trait SomeTrait {
    function sayHello(){
        echo "Hello my secret is ".static::$secret;
    }
}

abstract class AbstractClass implements SomeInterface{
    use SomeTrait;
}

class TestClass extends AbstractClass {
    static public  $secret = 12345;

    //function someInterfaceFunction(){
        //Trying to instantiate this class without this function uncommented will throw an error
        //Fatal error: Class TestClass contains 1 abstract method and must therefore be 
        //declared abstract or implement the remaining methods (SomeInterface::doSomething)
    //}
}

$test = new TestClass();

$test->sayHello();

Sin embargo, si necesita hacer cumplir que cualquier clase que use un Rasgo tiene un método en particular, creo que puede estar usando rasgos donde debería haber sido clases abstractas en primer lugar.

O que tienes tu lógica al revés. Debe requerir que las clases que implementen interfaces tengan ciertas funciones, no que si tienen ciertas funciones deban declararse como implementadoras de una interfaz.

Editar

En realidad, puede definir funciones abstractas dentro de Traits para forzar a una clase a implementar el método. p.ej

trait LoggerTrait {

    public function debug($message, array $context = array()) {
        $this->log('debug', $message, $context);
    }

    abstract public function log($level, $message, array $context = array());
}

Sin embargo, esto todavía no le permite implementar la interfaz en el rasgo, y aún huele como un mal diseño, ya que las interfaces son mucho mejores que los rasgos para definir un contrato que una clase debe cumplir.

Danack
fuente
2
¿Cómo sugeriría diseñar esto entonces? Tengo una clase humana, esta clase se abstrae en subclases basadas en Job, pero muchos de estos trabajos comparten características que se implementan mejor con código compartido (por ejemplo, tanto una secretaria como un programador necesitan el typemétodo ). ¿Puedes pensar en cómo podría implementarse esto sin rasgos?
scragar
@scragar, debería preguntar eso en programmers.stackexchange.com, pero la versión corta es que combinaría 'Human' con múltiples 'Jobs' para ser una clase de 'WorkingHuman'.
Danack
1
Uno mas. Si la interfaz define algún contrato de conocimiento y ese contrato es común para la mayoría de las implementaciones. Pero esas implementaciones tienen su propio tipo tres. Algo como Command con ContainerAwareInterface. Pero Comand tiene sus propias áreas específicas de uso. Así que necesito repetirme cada vez que necesito Container Awareness, pero si uso Trait no puedo definir su propio contrato para una interfaz específica. ¿Quizás los desarrolladores principales deberían considerar las interfaces Go-Type (por ejemplo, el tipo estructural)?
lazycommit
3
es realmente extraño, ya que en realidad mis colegas y yo solo usamos rasgos cuando queremos compartir código que se requiere en varias clases que implementan una interfaz pero que provienen de diferentes ancestros. Además, no hay una explicación razonable de por qué el compilador puede alterar el código dentro de la clase pero no las interfaces implementadas por esta clase. ... es simplemente una característica "faltante" ... "porque no puedes" explica esto de la mejor manera
Summer-Sky
5
Creo que los desarrolladores centrales de PHP deberían estudiar un poco de Scala, donde los rasgos se consideran tipos completos ... Me parece triste que PHP quiera mejorar gradualmente su sistema de escritura, pero no tiene en cuenta las implementaciones existentes que funcionan bien
Vincent Pazeller
28

Hay un RFC: Traits with interfaces sugiere que se agreguen al idioma:

trait SearchItem implements SearchItemInterface
{
    ...
}

Los métodos requeridos por la interfaz pueden ser implementados por el rasgo o declarados como abstractos, en cuyo caso se espera que la clase que usa el rasgo lo implemente.

Actualmente, esta característica no es compatible con el idioma, pero se está considerando (el estado actual del RFC es: En discusión ).

Ilija
fuente
Supongo que, si se confirma, la gente querrá que más y más funciones de las clases normales se implementen en los rasgos. Hasta que no haya diferencia entre ellos y tengamos algún tipo de rasgo de Frankenstein que no divida adecuadamente las preocupaciones y responsabilidades. Como subraya la mejor respuesta, los rasgos deben verse como una conveniencia de copia pasada; no debe cruzar demasiado los límites de las clases. Queremos que una clase implemente una interfaz, ya sea que la implementación provenga del código directo o del uso de un rasgo; permitir implementar interfaces en rasgos puede ser confuso y engañoso
Kamafeather
Una gran aplicación de rasgos es proporcionar una implementación predeterminada fácil de pegar de una interfaz. Si quisiera asegurarse de que un rasgo cumpliera con una interfaz, sería bueno tener la ayuda de los compiladores
The Mighty Chris
Esta propuesta no hace que una clase que use un rasgo implemente automáticamente esas interfaces de rasgos (eso no funcionaría ya que puede cambiar el nombre / reemplazar los métodos de rasgo). El argumento de la pendiente resbaladiza de que pasar esto de alguna manera pasará RFC futuras más agresivas no debería contener el agua
The Mighty Chris
10

[...] "diseñar" en el código que todas las clases que quieran usar mi rasgo tienen que implementar la interfaz. Eso permitiría al Trait usar métodos de clase definidos por la interfaz y asegurarse de que existan en la clase.

Esto suena muy razonable y no diría que tiene que haber algún problema con su diseño. Se han sugerido rasgos con esta idea en mente, consulte el segundo punto aquí:

  • Un rasgo proporciona un conjunto de métodos que implementan el comportamiento.
  • Un rasgo requiere un conjunto de métodos que sirven como parámetros para el comportamiento proporcionado.
  • [...]

Schärli et al, Traits: Composable Units of Behavior, ECOOP'2003, LNCS 2743, págs. 248–274, Springer Verlag, 2003, página 2

Por lo tanto, sería más apropiado decir que desea que un rasgo requiera una interfaz, no "implementarlo".

No veo una razón por la que debería ser imposible tener este "rasgo requiere (sus clases de consumidores para implementar) una función de interfaz" en PHP, pero actualmente parece que falta.

Como señala @Danack en su respuesta , puede usar funciones abstractas en el rasgo para "requerirlas" de las clases que usan el rasgo. Desafortunadamente, no puede hacer esto con funciones privadas .

usuario1460043
fuente
1

Estoy de acuerdo con la respuesta de @Danack, sin embargo la complementaré un poco.

La versión realmente corta es más simple porque no puedes. No es así como funcionan los rasgos.

Solo puedo pensar en algunos casos en los que lo que solicitas es necesario y es más evidente como un problema de diseño que como una falla de lenguaje. Solo imagina que hay una interfaz como esta:

interface Weaponize
{
    public function hasAmmunition();
    public function pullTrigger();
    public function fire();
    public function recharge();
}

Se ha creado un rasgo que implementa una de las funciones definidas en la interfaz pero en el proceso usa otras funciones también definidas por la interfaz , propenso al error de que si la clase que usa la característica no implementa la interfaz todo falla al tirar de la desencadenar

trait Triggerable
{
    public function pullTrigger()
    {
        if ($this->hasAmmunition()) {
            $this->fire();
        }
    }
}

class Warrior
{
    use Triggerable;
}

Una solución fácil es simplemente forzar a la clase que usa el rasgo a implementar esas funciones también:

trait Triggerable
{
    public abstract function hasAmmunition();
    public abstract function fire();

    public function pullTrigger()
    {
        if ($this->hasAmmunition()) {
            $this->fire();
        }
    }
}

Entonces, el rasgo no depende completamente de la interfaz , sino una propuesta para implementar una de sus funciones, ya que al usar el rasgo la clase demandará la implementación de los métodos abstractos.

Diseño final

interface Weaponize
{
    public function hasAmmunition();
    public function pullTrigger();
    public function fire();
    public function recharge();
}

trait Triggerable
{
    public abstract function hasAmmunition();
    public abstract function fire();

    public function pullTrigger()
    {
        if ($this->hasAmmunition()) {
            $this->fire();
        }
    }
}


class Warrior implements Weaponize
{
    use Triggerable;

    public function hasAmmunition()
    {
        // TODO: Implement hasAmmunition() method.
    }

    public function fire()
    {
        // TODO: Implement fire() method.
    }

    public function recharge()
    {
        // TODO: Implement recharge() method.
    }
}

Por favor disculpe mi ingles

NekoOs
fuente