Quantity

Objetivo

Associar um valor a uma unidade, possivelmente permitindo operações aritméticas.

Propósito

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.

Implementação

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));
    }
}

Discussão

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.

Exemplos na API padrão

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

Padrões associados

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.

Bibliografia

Scroll to Top