Existe um Code Smell muito comum, descrito no livro Refactoring do Martin Fowler, chamado de Obsessao Primitiva, que e o favorecimento de tipos primitivos como number, string ou um conjunto deles, ao inves de introduzir um objeto que seja capaz de não apenas representar um conjunto de informacoes mas também garantir que elas sejam validas.
So que não basta introduzir um objeto, ao contrario do que muitos pensam, o proposito de um objeto Não é ser apenas uma estrutura de dados de chaves e valores, como o Struct na linguagem C ou Go.
type Color struct {
red int
green int
blue int
}
var color Color;
color.red = 10;
color.green = 78;
color.blue = 140;
Repare que no caso do Struct que representa Color, apesar de agruparmos as suas propriedades como red, green e blue, não temos como garantir que elas estejam corretas, ou seja, que tenham valor entre 0 e 255.
Um objeto vai além de uma estrutura de dados, ele e composto por propriedades e comportamento e seu objetivo Não é somente de carregar informacoes mas também preservar a invariancia por meio do encapsulamento.
O papel do construtor Não é apenas de construir o objeto, e ter um ponto único de entrada para impedir que um objeto seja construído em estado invalido, caso contrario era so construir o objeto vazio e ir atribuindo qualquer valor para as suas propriedades. Lembrando que em algumas linguagens e possível ter mais de um construtor ou ainda utilizar padrões como Factory Method ou Builder para casos mais complexos.
export default class Coord {
private lat: number;
private long: number;
constructor(lat: number, long: number) {
if (lat < -90 || lat > 90) throw new Error("Latitude must be between -90 and 90 degrees");
if (long < -180 || long > 180)
throw new Error("Longitude must be between -180 and 180 degrees");
this.lat = lat;
this.long = long;
}
getLat() {
return this.lat;
}
getLong() {
return this.long;
}
}
Na classe Coord, o construtor não permite que os parametros lat e long sejam menores que -90 ou maiores que 90, impedindo a construção do objeto em estado invalido. Além disso essas características são privadas, ou seja, ninguém fora do objeto e capaz de modifica-las para qualquer valor e para obte-las foram criados dois metodos de consulta, que retornam um valor imutavel.
Uma vez construído e necessario impedir modificacoes que levem o objeto a um estado invalido e e por isso que existem modificadores de visibilidade como private, que deve ser aplicados nas propriedades que so podem ser modificadas mediante a execução de um determinado comportamento.
E se for necessario modificar o valor de lat ou long? Bom, nesse caso você poderia construir um novo objeto já que ao modificar qualquer um desses valores temos conceitualmente uma nova coordenada.
Essa e a característica fundamental dos Value Objects, substituir um ou mais tipos primitivos por um objeto que representa o seu valor, eventualmente também oferecendo determinados tipos de comportamento.
export default class Dimension {
private width: number;
private height: number;
private length: number;
private constructor(width: number, height: number, length: number) {
if (width <= 0) throw new Error("Invalid width");
if (height <= 0) throw new Error("Invalid height");
if (length <= 0) throw new Error("Invalid length");
this.width = width;
this.height = height;
this.length = length;
}
static createInCentimeters(width: number, height: number, length: number) {
return new Dimension(width / 100, height / 100, length / 100);
}
static createInMeters(width: number, height: number, length: number) {
return new Dimension(width, height, length);
}
getWidth() {
return this.width;
}
getHeight() {
return this.height;
}
getLength() {
return this.length;
}
getVolumeInMeters() {
return this.width * this.height * this.length;
}
}
Observe que na classe Dimension e possível criar o objeto com as dimensoes em metros ou centimetros por meio do padrao Static Factory Method descrito pelo Joshua Bloch no livro Effective Java, não confunda com Factory Method que e do GoF, e fornecer o comportamento que retorna o volume em metros cubicos.
Resumindo, um Value Object representa um valor e a comparacao também e por valor, não por identidade como seria feito em uma Entity, depois podemos falar mais sobre ela em um outro artigo. Por isso não existem metodos para modificar o estado interno do Value Object, sendo ele imutavel e caso precise ser modificado e so instanciar um novo objeto.
Eles podem ser um excelente ponto de partida na construção de um Domain Model já que tem uma modelagem mais simples e objetiva, são testáveis no nível de unidade e extremamente reusaveis.