Value Object

Objetivo

Encapsular um valor em um objeto junto com todas as operações sobre ele.

Propósito

Ao trabalhar com orientação a objetos temos a possibilidade de abstrair o mundo real em categorias e em classes de objetos. Contudo, várias vezes nos deparamos com objetos que podem ser utilizados em contextos variados porque apenas representam valores de algum atributo de outros objetos.

Value Object(Objeto Valor) é um padrão de design para modelar este tipo de objeto útil e recorrente auxiliando na modelagem de objetos mais complexos.

O propósito principal deste padrões é facilitar a manipulação de um valor através de uma tipagem forte fazendo o tipo pertencer a um classe de objetos. Porque o valor tem associadas a si diversas propriedades e operações é útil encapsulá-las em uma única classe. Estas propriedades e operações dizem respeito apenas ao tipo de valor que estamos encapsulando e são universais o suficiente para serem utilizadas em vários contextos.

A utilização deste padrão também ajuda a explicitar a intenção do programador e na documentação do código. Por exemplo, um método com a assinatura findByCode(String) não explicita que tipo de código deve estar contido na String. Sendo que o tipo String pode ser utilizado para conter praticamente qualquer outro tipo de dado, este método exige uma documentação explicita que defina o conteúdo válido para o parâmetro. Contudo, um método com a assinatura findByCode(Cpf) deixa claro que pretendemos procurar utilizando um código de CPF. Não apenas documenta a intensão do programador como ainda provê ao compilador uma forma de nos ajudar, pois agora ele só aceitará objetos do tipo CPF. Esta prática é conhecida como o uso de Tiny Types. O padrão Value Object é uma ótima forma de implementar Tiny Types.

As vantagens do padrão Value Object são:

  • Fornecer tipagem mais forte – É muito melhor usar BigDecimal do que String na assinatura de um método. Ou Date em vez de três inteiros.

  • Fornecer métodos de manipulação do valor de forma coesa – Se o objeto representa um numero porque não ter métodos plus() ou mutiply()? Este métodos se tornam aceleradores já que são usado inumeras vezes em vez de código que transforma o valor em algo manipulável e faz a operação e converte de volta. Repetir isso sempre que é necessário torna o codigo poluido e de dificil manutenção ( já para não dizer sensivel a erros). Além disso proibimos manipulações erradas, como por exemplo fazer contas com um numero de telefone. Isto é possivel ser usarmos um int para conter o número, mas não se usarmos um objecto Telephone que contém a mesma informação, mas não permite operações aritméticas.

Implementação

A implementação de um Value Object é simples à primeira vista. Queremos apenas encapsular um valor. Se o valor é numérico iremos provavelmente utilizar algum dos tipos numéricos primitivos ou arrays de char ou String. Podemos até mesmo criar estruturas complexas, como matrizes.

Por outro lado, não existe uma implementação especifica para o padrão Value Object já que dependente muito do tipo de valor em causa e das regras relativas a esse valor que queremos implementar. Contudo existem algumas propriedades da classe que implementa deste padrão.

Tomemos como exemplo a classe seguinte:

public class CPF implements Serializable{

	// método de criação usando valores equivalentes

	public static CPF valueOf(String value){
		if( value == null || value.trim().isEmpty()){
			return null;
		} else if (value.trim().length() != 9) {
			// validate
			throw new IllegalArgumentException();
		}
		return valueOf(value.toCharArray());
	}

	public static CPF valueOf(char[] value){
		if( value == null){
			return null;
		} else if ( value.length != 9) {
			// validate
			throw new IllegalArgumentException();
		}
		int[] algarims = new int[value.length];

		for (int i = 0; i < algarims.lengt; i++){
			if (Character.isDigit(c)){
				int[i] = (int)c;
			} else {
				// some is not an algarism
				throw new IllegalArgumentException();
			}
		}

		return new CPF(algarisms);
	}

	// estado interno
	private final int[] algarisms;

	private CPF(int[] algarisms){ // impede herança
		this.algarisms = algarisms;
	}

	public int algarismAt(int index){
		return algarisms[index];
	}

	// métodos de Object
	public boolean equals (Object other){
		return other instanceof CPF cpf
            && Arrays.equals(this.algarisms, cpf.algarisms));
	}

	public int hashCode(){
		return int[0] + 37 * int[4];
	}

	public String toString(){
		return Arrays.toString(algarisms);
	}
}

Primeiro (linha 1) Criamos uma classe que implementa Serializable . Isto não é obrigatório, mas normalmente não ha mal em tornar a classe de um Value Object serializável. Depois criamos métodos seguindo o padrão Static Factory Method que permitem criar o objeto a partir de valores equivalentes expressos em objetos mais simples. Normalmente primitivos da linguagems ou objetos da API padrão. Estes métodos são também aproveitados para consistir o estado do objeto que será criado. O método pode ter qualquer nome, mas o uso de valueOf já se tornou popular e é aquele usado pelos objetos da JSE que implementam este padrão.

Deixamos o construtor (lina 35) privado para evitar extensões do objeto que poderia danificar a sua imutabilidade. Repare-se que o valor é atribuído no construtor e nunca mais é alterado. Isto nos garante a imutabilidade. A imutabilidade, por sua vez permite garantir que o objeto possa ser utiliza em ambiente multi-thread e que possa ser utilizado por objetos e contextos diferentes.

Os métodos herdados de Object ( linhas 44 e seguintes) devem sempre ser implementados para qualquer objeto, mas a sua implementação é obrigatória para Value Object. Isto permite usar estes objetos como chaves de Map e membros de Set, assim como vazemos com Integer ou String.

Métodos utilitários que permitem de alguma forma acessar indiretamente o valor, ou uma perspectiva do valor do objeto, podem ser incluídos conforme a necessidade e o contexto de uso (linha 39).

Discussão

Quando a tecnologia Enterprise JavaBeans (EJB) foi lançada, ela assumia que os métodos eram invocados remotamente. Isso significava que se o método setName() da entidade Customer fosse invocada na máquina cliente a invocação aconteceria na realidade na máquina servidor.

O problema é que se o objeto tivesse 10 atributos (ou mais) aconteceriam 10 invocações remotas (ou mais). Rapidamente isso se mostrou um problema já que mesmo em uma rede veloz isso era um problema de eficiência. A solução foi criar um objeto auxiliar onde todos os modificadores eram invocados (localmente) e depois o objeto seria enviado ao bean de uma só vez. Uma só chamada remota faria a atribuição de todos os atributos. Este objeto de transferência rapidamente se tornou um padrão; o Transfer Object (TO), que na época foi batizado de Value Object (VO) ou Data Transfer Object (DTO). Todos sinônimos.

À medida que mais e mais pessoas usavam esta gambiarra do EJB ficou evidente que os Entity Beans tinham um uso falho. Duas respostas foram encontradas para este problema:

  1. Minimizar a comunicação remota quando o bean e o seu cliente estavam na mesma JVM. Isso levou à introdução das interfaces locais.

  2. Adbicar de usar a tecnologia EJB. Algumas soluções tecnicas apareceram, como o framework Spring.

Martin Fowler elaborou um catálogo de padrões uteis a quem constrói aplicações empresariais. Um desses padrões considerava que é muitas vezes útil encapsular valores em objetos em vez de usar tipos primitivos ou objetos padrão ( como String, por exemplo). Este padrão parte da ideia de que cada objeto desta classe representa um valor diferente de entre um conjunto possível. Exemplos simples deste tipo de objeto podem ser encontrados no próprio JDK como Integer, BigDecimal, Date e até String. Fowler chamou a este padrão Value Object criando uma colisão de nomes com o que era chamado de Value Object no mundo EJB. Mas a ideia agradou e encontrou adeptos na filosofia de Domain Driven Design. Padrões como Quantity e Money nascem diretamente da aplicação deste padrão.

O nome Value Object representava, portanto, dois padrões diferentes. Para quem conhece o padrão descrito por Fowler,o padrão original derivado do uso de EJB começou a ser chamado simplesmente DTO ou TO deixando a sigla VO para o novo padrão. Claro que esta distinção só é válida para quem conhece os dois padrões. Caso contrário a ambiguidade é um empecilho. O padrão Value Object referido aqui é aquele descrito por Fowler. Um objeto que representa um valor.

Exemplos na API padrão

Todos os objetos que herdam de Number seguem o padrão Value Object. Alguns apenas encapsulam um valor primitivo como Integer e Double enqaunto outros como BigDecimal e BigInteger que permitem operações de maior precisão e sem o limite binário dos tipos primitivos. Outro exemplo de Value Object é a famosa classe String que encapsula uma sequencia de caracteres. Além destes podemos ainda citar java.util.Date e suas extensões do pacote java.sql , java.sql.Date , java.sql.Time e java.sql.Timestamp.

Padrões relacionados

O padrão Value Object tem várias especializações. As mais importantes seriam o padrão Quantity o qual se especializa ainda mais no padrão Money. Estes padrões além de valores associam ainda unidades de forma que é possivel trabalhar com ambos ( valor e unidade) simultaneamente.

Quando algum valor especifico é muito usado pode ser boa ideia não criar muitos objetos para esse valor. Isso diminui o esforço da máquina virtual de ficar construindo e destruído objetos a todo o momento. Nesta situação é conveniente utilizar o padrão Shared Object mantendo alguns valores em memoria e utilizando o mesmo objeto para o mesmo valor. Um exemplo disto é o método valueOf() da classe Integer que mantém um cache de valores inteiros. Outro exemplo são as constantes BigDecimal.ZERO , BigDecimal.ONE e BigDecimal.TEN.

Controle a criação dos objetos de valor não é possível se deixarmos que o construtor seja utilizado diretamente. Para evitar isso e implementar o uso de Shared Object é boa ideia prover métodos estáticos de criação , seguindo o padrão Static Factory Method, como no exemplo de Integer.valueOf().

Scroll to Top