Trabalhando com Value Objects

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.

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).

17 comentários em “Trabalhando com Value Objects

Adicione o seu

  1. Excelente artigo, Robson. Usarei como referência para apresentar o conceito quando precisar.
    Uma provocação que gostaria de lhe fazer é: E quanto ao uso de structs ao invés de classes para representar Value Objects em C#?
    Abraços

    Curtir

    1. Obrigado, Rafael.
      Confesso que, por falta de costume, as structs ficaram esquecidas na minha vida 🙂 .. Me parece que o uso delas é tão especifico que nunca vi necessidade (e nem vi ninguém usando). Nunca li nada sobre elas que me fizesse pensar nelas com mais carinho!
      []s

      Curtir

  2. São uma boa opção para implementação de Value Objects, mas é necessário ter muito cuidado ao usá-las. Eu ainda não tenho opinião formada quanto a usá-las ou não como opção padrão. A impossibilidade de usar herança é o que mais me incomoda.

    Curtir

  3. Excelente artigo Robson! Estava com dúvidas nos conceitos de Value Objects e agora tenho uma visão muito melhor. Obrigado!

    Curtir

  4. Estou lendo o livro do Vaughn Vernon e ele explica exatamente como você, ou vice-versa, parabéns pelo post, muito bem explicado.

    Curtir

  5. Robson gostaria de tirar uma dúvida, eu entendi o conceito de value Object, minha pergunta é a seguinte :
    Suponha que eu tenha um ORM como o entity framework, uma classe cliente onde eu tenha um VO endereço, como vc faz o mapeamento disso para salvar no banco ?

    Curtir

    1. Olá, Roger

      Não saberei te responder assim de “bate-pronto”.
      Só dei algumas brincadas com o EF e faz muito, mas muito tempo que não pego na prática. Não sei como ele está hoje em maturidade para lidar como um modelo rico (não leio muitas coisas boas a esse respeito).

      Em se tratando de ORM, sempre trabalhei com o NHibernate em código de produção e com ele é bem simples fazer esse tipo de mapeamento. Na sua classe de mapeamento do Cliente, teria algo como: Component(c => c.Endereco), e todos os atributos do endereço seria mapeados em colunas na própria tabela do Cliente (você pode, inclusive, configurar no mapeamento qual será o prefixo das colunas do endereço, por exemplo, Endereco_Logradouro, Endereco_CEP,…).

      Sorry!
      []s

      Curtir

      1. Obrigado pela resposta Robson,

        Estava um pouco difícil de encontrar a forma de fazer, mas consegui encontrar, testei, e deu certo.
        Para ficar registrado aqui caso alguém tenha essa mesma dúvida, basta utilizar a tag [Complextype] na classe que é o value object.
        []s

        Curtir

      2. Legal, Roger.

        Obrigado por compartilhar a solução.

        Esse é um dos motivos de eu não gostar do EF. O domínio acaba sujo por anotações por causa da persistência. Se houver outra forma de fazer, por meio de alguma classe de mapeamento, seria o ideal, senão, paciência. 🙂
        []s

        Curtir

      3. Opa,

        Consegui descobrir como fazer via mapeamento, abaixo segue o exemplo :

        public class Customer
        {
        public Customer()
        {
        this.ShippingAddress = new Adress();
        }

        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public Adress ShippingAddress { get; set; }
        }

        public class Adress
        {
        public string Street { get; set; }
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
        //Aqui adiciono o meu tipo complexo (VO)
        modelBuilder.ComplexType();

        //Aqui faço o mapeamento da propriedade da VO para a tabela
        modelBuilder.Types().Configure(ctc => ctc.Property(cust => cust.ShippingAddress.Street).HasColumnName(“ShipAddress”));

        base.OnModelCreating(modelBuilder);
        }

        Fica de exemplo as duas formas agora, abraços.

        Curtir

Participe! Vamos trocar uma ideia sobre desenvolvimento de software!

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google

Você está comentando utilizando sua conta Google. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s

Blog no WordPress.com.

Acima ↑

%d blogueiros gostam disto: