Development

Microtypes

Passing Strings and doubles around in an application can significantly increase the likelihood of inadvertantly passing incorrect parameters to methods. By strongly typing all string and number parameter types, this can be simply avoided. Its then also possible to add behaviour to these types, meaning that “utility” classes are removed, keeping behaviour close to the objects that need it.

Numeric Microtype

Here is an example base class for a numeric microtype. It exposes basic numeric operations, and also allows for derived classes to specify the maximum amount of accuracy that the type will allow.

public abstract class DecimalValue<T extends DecimalValue> implements Comparable<T> {
    
    protected final BigDecimal value;
 
    protected DecimalValue(BigDecimal value) {
        try {
            this.value = value.setScale(scale(), rounding());
        } catch (ArithmeticException e) {
            throw new IllegalArgumentException(value + " is incompatible with rounding mode of " + rounding() + " given scale of " + scale(), e);
        }
    }
 
    protected abstract int scale();
 
    protected abstract RoundingMode rounding();
 
    @SuppressWarnings({"unchecked"})
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
 
        DecimalValue<T> that = (DecimalValue<T>) o;
 
        return this.value.equals(that.value);
    }
 
    @Override
    public String toString() {
        return value.toString();
    }
 
    public int compareTo(T that) {
        return this.value.compareTo(that.value);
    }
 
    public boolean isZero() {
        return value.compareTo(BigDecimal.ZERO) == 0;
    }
 
    public boolean isGreaterThan(T that) {
        return compareTo(that) == 1;
    }
 
    public boolean isGreaterThanOrEqualTo(T that) {
        return compareTo(that) >= 0;
    }
 
    public boolean isLessThan(T that) {
        return compareTo(that) == -1;
    }
 
    public boolean isLessThanOrEqualTo(T that) {
        return compareTo(that) <= 0;
    }
 
    public BigDecimal minus(final T that) {
        return this.value.subtract(that.value);
    }
 
    @Override
    public int hashCode() {
        return value.hashCode();
    }
 
    public static <T extends DecimalValue<T>> BigDecimal exposeRawValueForMarshallingOrFormattingOnlyNotForBusinessLogic(T thing) {
        return thing.value;
    }
 
    public static <T extends DecimalValue<T>> double exposeDoubleValue(T thing) {
        return thing.value.doubleValue();
    }
 
    // Protected so that subclasses can perform calculations upon one another
    protected static <T extends DecimalValue<T>> BigDecimal asBigDecimal(T decimalValue) {
        return decimalValue.value;
    }
 
    public String format(String pattern) {
        return format(pattern, value);
    }
 
    public static String format(String pattern, Number number) {
        return new DecimalFormat(pattern).format(number);
    }
}

A Concrete Type

Here is an example of a concrete numeric type.

public class Delta extends DecimalValue<Delta> {
    private Delta(BigDecimal value) {
        super(value);
    }
 
    public static Delta delta(String value) {
        return new Delta(new BigDecimal(value));
    }
 
    public static Delta delta(Double value) {
        return new Delta(new BigDecimal(value));
    }
 
    protected int scale() {
        return 8;
    }
 
    protected RoundingMode rounding() {
        return HALF_UP;
    }
}

A base string class

public abstract class DelegateStringId<T extends DelegateStringId<T>> implements Comparable<T> {
    public final String value;
 
    protected DelegateStringId(String value) {
        this.value = value;
 
        if(value == null) {
            throw new IllegalArgumentException("Cannot have a null " + getClass().getSimpleName());
        }
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
 
        DelegateStringId that = (DelegateStringId) o;
 
        return value.equals(that.value);
 
    }
 
    @Override
    public int hashCode() {
        return value.hashCode();
    }
 
    @Override
    public String toString() {
        return value;
    }
 
    public int compareTo(T other) {
        if ( this.getClass().equals(other.getClass())) {
            return this.value.compareTo(((DelegateStringId) other).value);
        }
        throw new ClassCastException("Can only compare exactly same types");
    }
}

A Concrete Class

Note that we use a static method, rather than a constructor to create the object, it has exactly the same effect, but is just that little bit more readable.

public class Isin extends DelegateStringId<Isin> {
 
    public Isin(final String value) {
        super(value);
    }
 
    public static Isin of(String isin) {
        return new Isin(isin);
    }
}

It makes the code read much more like english when you have

doSomeThing( Isin.of("DE00101110110"), Ric.of("VOD.L"));

rather than

doSomeThing( new Isin("DE00101110110"), new Ric("VOD.L"));
comments powered by Disqus