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 with
Scala), o ScalaTest pode oferecer todas essas opções sem a necessidade de criar nomes de classe WordSpecLikeWithMatchersAndFutures
ou 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 MyCustomTrait
compartilhar 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.