C# - Conceitos DDD - Value Object

 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)
        {
            return new NomeCompleto(nome, sobrenome);
        }

        private NomeCompleto(string nome, string sobrenome)
        {
            _nome = nome;
            _sobrenome = sobrenome;
        }

        public string Nome => _nome;
        public string Sobrenome => _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)
        {
            _id = Guid.NewGuid();
            _nome = NomeCompleto.Create(nome, sobrenome);

        }

        public Guid Id => _id;
        public NomeCompleto Nome => _nome;

        public void CorrigirNomeCliente(string nome, string sobrenome)
        {
            _nome = NomeCompleto.Create(nome, 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;

NotaGuid 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 class NomeCompleto : IEquatable<NomeCompleto>
    {
        private string _nome;
        private string _sobrenome;

        public override bool Equals(object obj)
        {
            return Equals(obj as NomeCompleto);
        }

        public bool Equals(NomeCompleto outronome)
        {
            return outronome != null &&
                   Nome == outronome.Nome &&
                   Sobrenome == outronome.Sobrenome;
        }

        public override int GetHashCode()
        {
            return HashCode.Combine(Nome, Sobrenome);
        }

        public static bool operator ==(NomeCompleto left, NomeCompleto right)
        {
            return EqualityComparer<NomeCompleto>.Default.Equals(left, right);
        }

        public static bool operator !=(NomeCompleto left, NomeCompleto right)
        {
            return !(left == right);
        }

        public static NomeCompleto Create(string nome, string sobrenome)
        {
            return new NomeCompleto(nome, sobrenome);
        }

        private NomeCompleto(string nome, string sobrenome)
        {
            _nome = nome;
            _sobrenome = sobrenome;
        }

        public string Nome => _nome;
        public string Sobrenome => _sobrenome;

    }
}

Assim, acabamos de definir um Value Object NomeCompleto que esta sendo usado pela Entidade Cliente.

E estamos conversados...


Referências:


José Carlos Macoratti