int horasDaAtividadeA = 5;
int horasDaAtividadeB = 3;
int horasTotais = horasDaAtividadeA * horasDaAtividadeB;
Associar um valor a uma unidade, possivelmente permitindo operações aritméticas.
Nas linguagens sempre temos disponíveis um conjunto de valores numéricos, normalmente inteiros ou de virgula flutuante (floating-point), mas em algoritmos que lidam com quantidades associadas a unidades - como distâncias, velocidades, períodos de tempo ou dinheiro - é importante considerar as operações aritméticas podem ser utilizadas.
Por exemplo, se usarmos um inteiro para contar as horas de uma atividade e tivermos duas atividades , poderíamos escrever este código:
int horasDaAtividadeA = 5;
int horasDaAtividadeB = 3;
int horasTotais = horasDaAtividadeA * horasDaAtividadeB;
Erroneamente o programador colocou um *
em vez de um +
e multiplicou horas por horas, o que, no caso, não tem significado.
O mesmo poderia acontecer se estivéssemos tratando com dinheiro ou qualquer outra grandeza.
Um outro erro que poderia acontecer seria:
int horasDaAtividadeA = 5;
int minutosDaAtividadeB = 3;
int horasTotais = horasDaAtividadeA + minutosDaAtividadeB;
Agora usamos o operador certo, mas estamos somando diferentes unidades. Isto não tem sentido, mas o domínio do tipo int
não é suficientemente rico para distinguir em qual unidade estamos trabalhando.
É necessário termos uma classe com atribuições especificas.
A implementação de um objeto no padrão Quantity tem duas partes: a representação e as operações. A representação é normalmente comum a todas as quantidades. Toda a quantidade tem que ter um valor e uma unidade. Para o valor podemos usar um inteiro, um double
ou um Ratio. Para a unidade teremos que criar uma classe de unidade.
A implementação base é portanto algo como:
public final class Quantity {
private final Ratio value;
private final Unit unit;
public Quantity (Ratio value, Unit unit){
this.value = value;
this.unit = unit;
}
public Ratio getValue(){
return this.value;
}
public Unit getUnit(){
return this.value;
}
// no setters
public boolean equals(Object obj){
return obj instanceof Quantity other
&& other.value.equals(this.value)
&& other.unit.equals(this.unit);
}
public int hashCode(){
return this.value.hashCode() + 31 * this.unit.hashCode();
}
public String toString(){
return this.value.toString() + " " + this.unit.toString();
}
}
O objeto que implementa o padrão Quantity é imutável e formado por um valor e uma unidade também imutáveis.
Dependendo do que a classe representa ela irá ter operações aritméticas apropriadas. Por exemplo, uma quantidade de distância poderia ter um método que multiplica por outra distância e retorna uma área, mas uma quantidade de dinheiro não poderia ter esse método já que multiplicar um valor de dinheiro por outro não tem significado.
As operações de soma e subtração são mais comuns. Para realizar essas operações temos que garantir a unidade é a mesma:
public final class Quantity {
/// código anterior
...
public Quantity plus (Quantity other){
if (!this.unit.equals(other.unit)){
throw new UnitNotTheSameException();
}
return new Quantity ( this.value.plus(other.value), this.unit);
}
}
Para multiplicações é um pouco mais complexo por que a unidade teria que se multiplicar também.
public final class Quantity {
/// código anterior
...
public Quantity multiply (Quantity other){
return new Quantity ( this.value.multiply(other.value), this.unit.multiply(other.unit));
}
}
O padrão Quantity extende o padrão Value Object com o conceito de unidade e a verificação se as operações aritméticas são realizadas com as mesmas unidades. Isto ajuda a melhorar o código e os testes.
É possível implementar frameworks genéricos de quantidades que são úteis quando estamos tentando utilizar várias unidades simultaneamente. Por exemplo, calcular uma velocidade:
Quantity distance = new Quantity(20, SI.Meter);
Quantity time = new Quantity(10, SI.Second);
Quantity velocity = distance.over(time);
assertEquals(new Quantity(2, SI.MeterPerSecond) , velocity);
Mas normalmente é mais comum implementar objetos específicos ao domínio em causa como distâncias, ângulos, ou tempos. No caso de dinheiro existe o padrão Money que se utiliza dos conceitos específicos relacionados a dinheiro para simplificar a implementação de Quantity
e Unit
.
Na API java padrão podermos encontrar a classe java.time.Duration
que segue o padrão Quantity de uma forma simples.
Duration aDay = Duration.of(1, ChronoUnit.DAYS);
Neste caso a unidade é a interface TemporalUnit
que é implementada pela classe ChronoUnit
, que é também um enum.
A API JSR 385 é uma implementação do padrão Quantity direcionado a unidades de medida
Como foi referido, o padrão Quantity é uma especificação do padrão Value Object e pode ser ainda especificado pelo padrão Money.
O padrão Quantity também se relaciona ao padrão Ratio já que ele pode ser usado para representar o valor da quantidade e prover as operações aritméticas sobre o valor.
Para implementações mais complexas e genéricas de Quantity o padrão Composite Object também pode ser útil, especialmente para implementar operações de multiplicação de unidades.
[1] Martin Fowler Analysis Patterns: Reusable Object Models: Addison-Wesley Professional (1997)
[2] Martin Fowler _ Quantity_ http://www.dsc.ufcg.edu.br/~jacques/cursos/map/recursos/fowler-ap/Analysis%20Pattern%20Quantity.htm