Primitivas, como string
ou int
, não têm significado em um domínio comercial. Uma conseqüência direta disso é que você pode usar um URL por engano quando é esperado um ID do produto ou usar quantidade ao esperar o preço .
É também por isso que o desafio Object Calisthenics apresenta empacotamento de primitivas como uma de suas regras:
Regra 3: agrupar todas as primitivas e seqüências de caracteres
Na linguagem Java, int é um objeto primitivo, não real, por isso obedece a regras diferentes dos objetos. É usado com uma sintaxe que não é orientada a objetos. Mais importante, um int por si só é apenas um escalar, por isso não tem significado. Quando um método usa int como parâmetro, o nome do método precisa executar todo o trabalho de expressar a intenção. Se o mesmo método demorar uma hora como parâmetro, é muito mais fácil ver o que está acontecendo.
O mesmo documento explica que há um benefício adicional:
Objetos pequenos como Hour ou Money também nos dão um lugar óbvio para colocar comportamentos que, de outra forma, seriam espalhados por outras classes .
De fato, quando as primitivas são usadas, geralmente é extremamente difícil rastrear a localização exata do código relacionado a esses tipos, geralmente levando à duplicação grave do código . Se houver Price: Money
classe, é natural encontrar a faixa de verificação dentro. Se, em vez disso, é usado um int
(pior, a double
) para armazenar os preços dos produtos, quem deve validar o intervalo? O produto? O desconto? O carrinho?
Finalmente, um terceiro benefício não mencionado no documento é a capacidade de alterar relativamente fácil o tipo subjacente. Se hoje meu ProductId
tem short
como seu tipo subjacente e, posteriormente, eu preciso usá-lo int
, é provável que o código a ser alterado não abranja toda a base de código.
A desvantagem - e o mesmo argumento se aplica a todas as regras do exercício Object Calisthenics - é que, se rapidamente se torna esmagador demais para criar uma classe para tudo . Se Product
contém o ProductPrice
que herda do PositivePrice
qual herda o Price
qual, por sua vez Money
, não é uma arquitetura limpa, mas uma bagunça completa em que, para encontrar uma única coisa, um mantenedor deve abrir algumas dezenas de arquivos todas as vezes.
Outro ponto a considerar é o custo (em termos de linhas de código) da criação de classes adicionais. Se os invólucros forem imutáveis (como deveriam ser normalmente), significa que, se usarmos C #, você precisará ter, dentro do invólucro, pelo menos:
- O getter da propriedade,
- Seu campo de apoio,
- Um construtor que atribui o valor ao campo de suporte,
- Um costume
ToString()
,
- Comentários da documentação XML (que cria muitas linhas),
- A
Equals
e a GetHashCode
substitui (também muito LOC).
e eventualmente, quando relevante:
- Um atributo DebuggerDisplay ,
- Uma substituição
==
e !=
operadores,
- Eventualmente, uma sobrecarga do operador implícito de conversão para converter sem problemas de e para o tipo encapsulado,
- Contratos de código (incluindo o invariante, que é um método bastante longo, com seus três atributos),
- Vários conversores que serão usados durante a serialização XML, serialização JSON ou armazenamento / carregamento de um valor para / de um banco de dados.
Cem LOC para um invólucro simples o tornam bastante proibitivo, e é também por isso que você pode ter certeza absoluta da lucratividade a longo prazo desse invólucro. A noção de escopo explicada por Thomas Junk é particularmente relevante aqui. Escrever cem LOCs para representar um ProductId
usado em toda a sua base de código parece bastante útil. Escrever uma classe desse tamanho para um pedaço de código que cria três linhas em um único método é muito mais questionável.
Conclusão:
Agrupe primitivas em classes que tenham significado no domínio comercial do aplicativo quando (1) ajudar a reduzir erros, (2) reduzir o risco de duplicação de código ou (3) ajudar a alterar o tipo subjacente posteriormente.
Não agrupe automaticamente todos os primitivos encontrados no seu código: há muitos casos em que o uso string
ou int
é perfeitamente adequado.
Na prática, public string CreateNewThing()
retornar uma instância da ThingId
classe em vez de string
pode ajudar, mas você também pode:
Retorne uma instância da Id<string>
classe, que é um objeto do tipo genérico, indicando que o tipo subjacente é uma sequência. Você tem o benefício da legibilidade, sem a desvantagem de ter que manter muitos tipos.
Retorne uma instância da Thing
classe. Se o usuário precisar apenas do ID, isso pode ser feito facilmente com:
var thing = this.CreateNewThing();
var id = thing.Id;