Hoje vamos apresentar o conceito de Value Object (Objeto de Valor). |
O conceito de Value Object foi cunhado por Eric Evans em seu livro Domain-Driven Design: Tackling Complexity in the Heart of Software.
Um Value Object(VO) na abordagem do Domain Driven Design é um objeto que representa um valor e não possui identidade, servindo para dar característica a outro objeto. Dessa forma a identidade de um VO está ligada à composição de seus valores e não a uma propriedade de identidade.
Com base neste conceito dois Value Objects são iguais quando possuem o mesmo valor não sendo necessariamente o mesmo objeto.
Como exemplo de Value Objects podemos citar objetos que representam Nome, Endereco, Email, Dinheiro, etc.
Um objeto Dinheiro com valor R$ 5,00 é igual a outro objeto de mesmo valor; um objeto Nome com valor 'Jose' é igual a outro objeto de mesmo valor não importando a instância do objeto.
Se um objeto dentro do seu domínio não guarda estado e não precisa existir se não estiver relacionado com nenhuma outra Entidade, então considere-o como forte concorrente a ser um VO.
Os Value Objects devem ser imutáveis e pequenos e representam algo único como quantidades, descrições simples, valores, etc.
As diretrizes que
podemos usar para projetar um Value Object são:
- Ser imutável
- Seu único uso é como propriedade de uma ou mais entidades ou mesmo outro VO;
- Saber usar todas as suas propriedades ao realizar qualquer tipo de
verificação de igualdade, incluindo aquelas que baseiam sua comparação em
códigos hash;
- Sua lógica não deve ter efeitos colaterais no estado fora do VO;
Um exemplo de um Value Object : NomeCompleto
A seguir veremos um tipo que pode ser reutilizado em todo o sistema e que representa o nome de uma pessoa que vamos chamar de NomeCompleto. Podemos usar este tipo para Clientes, Usuários, Fornecedores, etc.
Vamos partir da definição da seguinte regra de negócio :
1- Toda a pessoa deve ter um nome e um sobrenome
Com base nisso definimos a classe NomeCompleto com o código a seguir:
public class NomeCompleto { private string _nome; private string _sobrenome;
public static NomeCompleto Create(string nome, string sobrenome) |
Analisando o código temos que :
1 - Temos dois campos privados _nome e _sobrenome
2 - O método factory Create obriga que na criação de um NomeCompleto temos que passar as duas partes do nome. Assim nunca teremos um estado inválido;
3 - O factory passa esses valores para o construtor que define os valores do campo;
4 - A seguir atribuímos os valores às propriedades Nome e Sobrenome usando o recurso expression body. As propriedades somente sabem retornar os valores dos campos.
Vamos destacar o seguinte nesta classe :
A seguir temos o código de uma classe Cliente que usa o Value Object NomeCompleto:
public class Cliente { private Guid _id; private NomeCompleto _nome;
public Cliente(string nome, string sobrenome) } public void CorrigirNomeCliente(string nome, string sobrenome) |
Na classe Cliente temos que :
- O construtor exige que seja fornecido os valores de nome e sobrenome e a seguir os usa para criar um objeto NomeCompleto junto com um Guid gerado para sua identidade;
Nota: Guid ou Globally Unique Identifier - É um identificador único universal é um número de 128 bits usado para identificar informações.
- Se precisar alterar o nome, você pode chamar o método CorrigirNomeCliente para recriar esse nome de imediato.
O DDD nos orienta
a pensar sobre os comportamentos em nossas entidades, e o método
CorrigirNomeCliente
representa um
comportamento necessário para resolver um problema específico.
- Só porque NomeCompleto é imutável, não significa
que o tipo não pode ser inteligente. Se precisarmos de outras maneiras de
visualizar nomes podemos incorporar os comportamentos no tipo
NomeCompleto.
Por exemplo, se eu quiser apresentar o nome completo em ordem alfabética pelo sobrenome posso implementar estes comportamentos na entidade Cliente.
Assim NomeCompleto agora é um tipo imutável que possui dois valores e nenhum identificador.
Para que este NomeCompleto seja um Value Object legítimo ainda preciso garantir que posso comparar dois tipos de NomeCompleto para determinar a igualdade.
E preciso de
flexibilidade para fazer isso usando o método Equals
herdado de todos os objetos na linguagem C#, bem usar os operadores
== ou! =.
Comparar duas instâncias deste objeto de valor com Equals
significa comparar cada um dos valores nos objetos.
A seguir temos a implementação dos métodos de comparação :
using System; using System.Collections.Generic;
namespace CShp_VO1 public override bool Equals(object obj) public bool Equals(NomeCompleto outronome) public override int GetHashCode() public static bool operator ==(NomeCompleto left, NomeCompleto right) public static bool operator !=(NomeCompleto left, NomeCompleto right) public static NomeCompleto Create(string nome, string sobrenome) |
Assim, acabamos de definir um Value Object NomeCompleto que esta sendo usado pela Entidade Cliente.
E estamos conversados...
Referências:
ADO .NET - Acesso Assíncrono aos dados
C# - Programação Funcional - Exemplos
C# - Coleções Imutáveis
C# 9.0 - Apresentando Records
C# - Os 10 Erros mais comuns dos iniciantes
C# - Otimizando o código
C# - Apresentando Value Object
ASP .NET Core - Usando conceitos do DDD
.NET - Conceitos DDD para iniciantes
Domain Driven Design - Área de Aplicação