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