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