Quando usar traços, em oposição à herança e composição?

9

Existem três maneiras comuns, AFAIK, de implementar a reutilização quando se trata de OOP

  1. Herança: geralmente representar é um relacionamento (um pato é um pássaro)
  2. Composição: geralmente para representar um relacionamento tem (um carro tem um motor)
  3. Traits (por exemplo, a palavra-chave trait em PHP): ... não tenho muita certeza disso

Embora pareça para mim que os traços possam implementar relacionamentos has-a e is-a, não tenho muita certeza de que tipo de modelagem foi projetado. Para que tipo de situações as características foram projetadas?

Extrakun
fonte
Você pode explicar um pouco mais sobre traços, mas traços poderiam ser apenas outra palavra para composição?
Trilarion
11
Eu realmente gostaria de saber por que também. Não os usei uma vez.
Andy

Respostas:

5

Traços são outra maneira de fazer composição. Pense neles como uma maneira de compor todas as partes de uma classe em tempo de compilação (ou tempo de compilação JIT), montando as implementações concretas das partes necessárias.

Basicamente, você deseja usar características quando se encontra fazendo aulas com diferentes combinações de recursos. Essa situação surge com mais frequência para as pessoas que escrevem bibliotecas flexíveis para outras pessoas consumirem. Por exemplo, aqui está a declaração de uma classe de teste de unidade que escrevi recentemente usando o ScalaTest :

class TestMyClass
  extends WordSpecLike
  with Matchers
  with MyCustomTrait
  with BeforeAndAfterAll
  with BeforeAndAfterEach
  with ScalaFutures

Estruturas de teste de unidade tem uma tonelada de diferentes opções de configuração, e cada equipe tem diferentes preferências sobre como eles querem fazer as coisas. Ao colocar as opções em traços (que são misturados no withScala), o ScalaTest pode oferecer todas essas opções sem a necessidade de criar nomes de classe WordSpecLikeWithMatchersAndFuturesou uma tonelada de sinalizadores booleanos de tempo de execução WordSpecLike(enableFutures, enableMatchers, ...). Isso facilita seguir o princípio Aberto / Fechado . Você pode adicionar novos recursos e novas combinações de recursos simplesmente adicionando uma nova característica. Também facilita seguir o Princípio de Segregação de Interface , porque você pode facilmente colocar funções não universalmente necessárias em uma característica.

Os traços também são uma boa maneira de colocar código comum em várias classes que não fazem sentido para compartilhar uma hierarquia de herança. A herança é um relacionamento muito estreitamente associado, e você não deve pagar esse custo se puder ajudá-lo. Traços são um relacionamento muito mais frouxamente acoplado. No meu exemplo acima, eu costumava MyCustomTraitcompartilhar facilmente uma implementação de banco de dados simulada entre várias classes de teste não relacionadas.

A injeção de dependência alcança muitos dos mesmos objetivos, mas em tempo de execução, com base na entrada do usuário, em vez de no tempo de compilação, com base na entrada do programador. Os traços também se destinam mais a dependências que fazem parte semanticamente da mesma classe. Você está montando as partes de uma classe em vez de fazer chamadas para outras classes com outras responsabilidades.

As estruturas de injeção de dependência alcançam muitos dos mesmos objetivos em tempo de compilação, com base nas informações do programador, mas são uma solução alternativa para linguagens de programação sem suporte adequado às características. Os traços trazem essas dependências para o domínio do verificador de tipos do compilador, com sintaxe mais limpa, com um processo de construção mais simples, que faz uma distinção mais clara entre as dependências em tempo de compilação e tempo de execução.

Karl Bielefeldt
fonte
Olá, esta é uma ótima resposta. Posso perguntar como decidir quando usar características e quando usar injeção de dependência. que parece conseguir o mesmo.
Extrakun 22/03/16
Observação astuta. Veja minha edição.
Karl Bielefeldt