Em design de software, é comum termos objetos que simplesmente fazem alguma coisa ou caracterizam alguma outra coisa. Eles mais se assemelham a um valor (como DateTime ou string) do que a um objeto propriamente dito, com estado e um ciclo de vida.

Identificar esse tipo de objeto, conhecido por Value Object, em nosso design, pode ajudar a simplifica-lo.

Este artigo faz uma introdução ao assunto. Espero que gostem.

O QUE SÃO

De forma sucinta, podemos dizer que Value Objects são objetos sem identidade conceitual e que dão característica a algum outro objeto. Em geral, estamos interessados no que eles fazem e não em quem eles são.

Um exemplo clássico seria criarmos um objeto para representar dinheiro em nossa aplicação. Em geral, um objeto Dinheiro de valor R$ 10,00 é igual a outro objeto Dinheiro de mesmo valor, ou seja, não importa de qual instância estamos falando: R$ 10,00 são R$ 10,00. Por isso, eles não possuem identidade conceitual. O que importa são seus atributos.

Outros exemplos de conceitos que poderiam ser modelados como Value Objects seriam: CPF, telefone e endereço.

CARACTERÍSTICAS

Vamos ver uma forma de implementar o Value Object Dinheiro acima mencionado e com isso apresentar mais alguma características importantes desse tipo de objeto.

public class Dinheiro
{
    public string Moeda { get; private set; }
    public decimal Valor { get; private set; }

    public Dinheiro(string moeda, decimal valor)
    {
        // validar parametros...
        Moeda = moeda;
        Valor = valor;
    }

    public Dinheiro SomarCom(Dinheiro dinheiro)
    {
        return new Dinheiro(dinheiro.Moeda, Valor + dinheiro.Valor);
    }
}

Com o pequeno código acima, já podemos visualizar outras características dos VOs.

Um delas é que um VO pode ser usado para agrupar informações relacionadas em um único conceito (Conceptual Whole). Notem que Dinheiro agrupa dois atributos fortemente relacionados: Moeda e Valor.

Outra característica é a imutabilidade: os atributos do objeto nunca são alterados (percebam os setters privados e ausência de métodos que alterem o estado do objeto). “Em português”, R$ 10,00 é e sempre será R$ 10,00. Não faz sentido alterar a moeda ou o valor. R$ 10,00 não “vira” R$ 15,00 ou US$ 10,00, caso contrário, seria outro dinheiro.

Observem que a imutabilidade também é garantida por meio de funções sem efeitos colaterais. O método SomarCom não altera o estado do objeto. Ao invés disso, retorna um novo objeto com o valor da soma.

IGUALDADE

Agora, outro detalhe importantíssimo. Já que um VO não possui identidade conceitual, como garantir que um objeto Dinheiro contendo R$ 10,00 seja considerado IGUAL a outro objeto de mesmo valor?

Em outras palavras, como garantir que o teste abaixo passe?

[Test]
public void Deve_comparar_dois_valores()
{
    var dinheiro = new Dinheiro("R$", 10.00);
    var outroDinheiro = new Dinheiro("R$", 10.00);
    Assert.IsTrue(dinheiro.Equals(outroDinheiro));
}

Apesar de terem os mesmos valores, “dinheiro” e “outroDinheiro” ainda são dois objetos distintos em memória. Sendo assim, o teste inicialmente falha. Para fazermos o teste passar, expressando realmente o conceito de VO, devemos sobrescrever Equals:

public class Dinheiro
{
    // (restante do código)
    public override bool Equals(object obj)
    {
        if (ReferenceEquals(this, obj)) return true;
        var dinheiro = obj as Dinheiro;
        if (ReferenceEquals(null, dinheiro)) return false;
        return (Moeda == dinheiro.Moeda && Valor == dinheiro.Valor);
    }

    public override int GetHashCode()
    {
        return Moeda.GetHashCode() ^ Valor.GetHashCode();
    }
}

Basicamente, Equals checa três coisas: se são o mesmo objeto, se são objetos do mesmo tipo e se todos os atributos são iguais. Ao rodar o teste anterior, ele deverá passar (experimente testar com moeda e valores diferentes). Vejam também o uso de todos os atributos para obtenção do hashcode do objeto.

É uma boa ideia, porém não obrigatória, sobrescrever os operadores == e != (fica como exercício para vocês!).

VALUE OBJECTS E DOMAIN-DRIVEN DESIGN

O padrão Value Object é parte fundamental de Domain-Driven Design, como um dos seus “building-blocks”. Isso significa que não temos que limitar o uso de VOs para conceitos mais genéricos como CPF e Dinheiro. Podemos modelar nosso domínio utilizando VOs para descrever conceitos importantes referentes ao problema de negócio em questão.

Supondo que nosso domínio seja financiamento de imóveis, poderíamos modelar o conceito de “Prestação” como um Value Object, composto pelos atributos “Amortização” e “Juros”, para tornar explícito o conceito de prestação e facilitar a manipulação dos atributos “Amortização” e “Juros”. Imaginem ficar passando o par “Amortização”/”Juros” para todo lugar da aplicação?

VANTAGENS E DESVANTAGENS

O uso de VOs tem uma série de vantagens:

– Proporcionam o uso de orientação a objetos de verdade, encapsulando comportamento em um objeto ao invés de trabalharmos com tipos primitivos e espalharmos regras em diversos pontos da aplicação (como em métodos de extensão, por ex.);
– Como são imutáveis, dois objetos podem compartilhar o mesmo VO sem perigo de uma alteração no VO se propagar para todos eles;
– Também graças à imutabilidade, podemos passá-los como parâmetros de métodos sem o risco de uma alteração indevida pelo método-cliente;
– Tornam explícitos conceitos do domínio antes escondidos em atributos relacionados;
– São simples de implementar e testar.

Como fazer as coisas bem feitas dá trabalho, podemos dizer que a “desvantagem” do padrão não está propriamente em usá-lo e sim em identificar objetos na aplicação que possam ser implementados como um Value Object.

É preciso entender bem o problema, discutir e sempre questionar se o objeto é algo que define algum outro objeto e não possui identidade conceitual ou se realmente estamos interessados em diferenciar cada instância e acompanhar o ciclo de vida do objeto (o objeto é mutável ao longo do tempo).

CONCLUSÃO

Neste artigo, vimos um pouco sobre o padrão Value Object, um importante padrão a ser considerado no design do software, fácil de ser implementado e que pode evitar dores de cabeça no design. Conhecemos suas principais características com um exemplo simples de código e vimos suas principais vantagens.

Para aqueles que quiserem aprofundar mais no assunto recomendo a leitura do mesmo nos livros: Patterns of Enterprise Application Architecture (Fowler), Domain-Driven Design (Evans) e Implementing Domain-Driven Design (Vernon).

Por hoje é só!

[]s