Shopping cart

Subtotal $0.00

View cartCheckout

Building better devs

TnewsTnews
  • Home
  • .NET
  • Value Objects em C#: Clean Code e DDD na prática
.NET

Value Objects em C#: Clean Code e DDD na prática

Email : 2

🧠 O que são Value Objects?

Value Object (Objeto de Valor) é um padrão amplamente utilizado no Domain-Driven Design (DDD), onde uma classe é definida pelo seu valor e não por sua identidade. Ou seja, dois Value Objects com os mesmos dados são considerados iguais, mesmo que sejam instâncias diferentes.

Diferente de entidades, Value Objects não têm identidade própria (ID). Eles são imutáveis, comparáveis por valor, e geralmente utilizados para representar conceitos que não se alteram sozinhos, como:

  • Endereço
  • CPF
  • Nome completo
  • Dinheiro
  • Coordenadas geográficas

✨ Por que usar Value Objects?

  1. Promove imutabilidade
  2. Facilita testes (não depende de banco de dados)
  3. Garante integridade de dados
  4. Evita código duplicado
  5. Expressa melhor a intenção no domínio

🧼 Como Value Objects ajudam no Clean Code?

  • Nomeiam comportamentos: encapsulam lógica relacionada àquele valor, tornando o código mais expressivo.
  • Eliminam código solto e validações repetidas: validam os dados dentro do próprio objeto.
  • Facilitam a reutilização e manutenibilidade: ao encapsular regras e comportamento no lugar certo.

🧭 Como Value Objects ajudam no DDD?

  • Modelam o domínio com precisão: por exemplo, ao invés de tratar um decimal como salário, você tem um Salario com validações, arredondamentos e lógica específica.
  • Deixam as entidades mais limpas: tirando delas a responsabilidade por validar ou interpretar dados primitivos.

✅ Regras gerais para criar um Value Object:

  1. Imutável após criação
  2. Não possui identidade própria
  3. Comparável por valor
  4. Autocontido (validações e comportamento encapsulados)

💻 Exemplo completo: Criando um Value Object CPF

using System;
using System.Text.RegularExpressions;

public class Cpf : IEquatable<Cpf>
{
    public string Numero { get; }

    public Cpf(string numero)
    {
        if (string.IsNullOrWhiteSpace(numero))
            throw new ArgumentException("CPF não pode ser vazio.");

        numero = ApenasNumeros(numero);

        if (numero.Length != 11 || !EhValido(numero))
            throw new ArgumentException("CPF inválido.");

        Numero = numero;
    }

    private string ApenasNumeros(string input)
    {
        return Regex.Replace(input, "[^0-9]", "");
    }

    // Lógica de validação baseada nos dígitos verificadores
    private bool EhValido(string cpf)
    {
        if (new string(cpf[0], cpf.Length) == cpf)
            return false;

        int[] multiplicador1 = { 10, 9, 8, 7, 6, 5, 4, 3, 2 };
        int[] multiplicador2 = { 11, 10, 9, 8, 7, 6, 5, 4, 3, 2 };

        var tempCpf = cpf.Substring(0, 9);
        var soma = 0;

        for (int i = 0; i < 9; i++)
            soma += int.Parse(tempCpf[i].ToString()) * multiplicador1[i];

        var resto = soma % 11;
        resto = resto < 2 ? 0 : 11 - resto;

        var digito = resto.ToString();
        tempCpf += digito;
        soma = 0;

        for (int i = 0; i < 10; i++)
            soma += int.Parse(tempCpf[i].ToString()) * multiplicador2[i];

        resto = soma % 11;
        resto = resto < 2 ? 0 : 11 - resto;

        digito += resto.ToString();
        return cpf.EndsWith(digito);
    }

    public override string ToString() => Convert.ToUInt64(Numero).ToString(@"000\.000\.000\-00");

    // Implementação de igualdade baseada no valor
    public override bool Equals(object obj) => Equals(obj as Cpf);

    public bool Equals(Cpf other) => other != null && Numero == other.Numero;

    public override int GetHashCode() => Numero.GetHashCode();
}

✍️ Usando o Cpf em uma entidade:

public class Cliente
{
    public Guid Id { get; private set; }
    public string Nome { get; private set; }
    public Cpf Cpf { get; private set; }

    public Cliente(string nome, Cpf cpf)
    {
        Id = Guid.NewGuid();
        Nome = nome ?? throw new ArgumentNullException(nameof(nome));
        Cpf = cpf ?? throw new ArgumentNullException(nameof(cpf));
    }
}

📌 E como seria usar isso?

var cpf = new Cpf("123.456.789-09");
var cliente = new Cliente("Lucas Dalcolmo", cpf);

Console.WriteLine(cliente.Cpf); // 123.456.789-09

🔄 Comparação de CPF como Value Object:

var cpf1 = new Cpf("12345678909");
var cpf2 = new Cpf("123.456.789-09");

Console.WriteLine(cpf1.Equals(cpf2)); // true

Mesmo sendo instâncias diferentes, o valor é o mesmo — por isso são considerados iguais. Isso seria impossível se usássemos string.


🧩 Outros exemplos comuns de Value Object

  • Email: com regex de validação e formatação.
  • Telefone: com DDD, DDI e máscara.
  • Dinheiro: com moeda, casas decimais e arredondamento.
  • Endereço: com CEP, cidade, estado e regras de formatação.
  • Slug: versões amigáveis de nomes/títulos para URLs.

🧪 Testando Value Objects

[Fact]
public void Deve_Lancar_Excecao_Para_Cpf_Invalido()
{
    Assert.Throws<ArgumentException>(() => new Cpf("00000000000"));
}

[Fact]
public void Deve_Comparar_Cpfs_Iguais()
{
    var c1 = new Cpf("12345678909");
    var c2 = new Cpf("123.456.789-09");

    Assert.Equal(c1, c2);
}

📦 Value Object vs Entidade vs DTO

ConceitoTem ID?É Imutável?Representa domínio?
EntidadeSimNãoSim
Value ObjectNãoSimSim
DTONãoPode serNão (camada de transporte)

🏁 Conclusão

Value Objects são um dos pilares do DDD e uma excelente prática de Clean Code. Eles trazem clareza, segurança e coerência ao modelo de domínio, encapsulando regras e representando valores com significado.

Eles facilitam testes, evitam erros comuns, e tornam o código mais legível e resiliente. Em vez de confiar em strings, ints ou decimals soltos, modele valores com comportamento próprio.

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Posts