How Statistics Can Enrich Domain-Driven Design’s Building Blocks? (Part-4)

Ruhollah Delpak
6 min readMar 2, 2023

Part 1: Values, variables from a statistics point of view

Part 2: Definition of Nominal, Ordinal, Interval and Ratio variables

Part 3: Introducing Nominal Value Object and Ordinal Value Object

In the previous parts, I explained that how the method of categorizing variables in statistics, can extend DDD’s tactical building blocks and eventually helps us achieve a more sophisticated and richer domain model. Now, after that Nominal Value Object and Ordinal Value Object were described in previous part, in this section I will introduce Interval Value Object and then Ratio Value Object.

Four alternatives to Value object and relation of them with other DDD’s building blocks.

Interval Value Object:

Essence of interval variable and its usage in statistics, was discussed here. Once again, let’s review the characteristics of this type of variable:

  1. They have all the characteristics of ordinal variables.
  2. The differences between values is quantitative as it can quantify the difference between the values and they can be added and subtracted together or the differences between two values can be compared.
  3. The zero point in this variable is a contractual value. (such as temperature in Celsius or sound intensity in Decibel)

Example:

With this explanation, it is necessary to design the Interval Value Object in such a way that:

  • Two values can be compared together.
  • Two values can be added or subtracted together and result should be same type. This desirable restriction, is compatible with the idea of closure of operations. For example, the sum of two variables of the temperature type, will still be a variable of the temperature type.
  • Be able to set its zero point arbitrary.
Mathematical Features of Interval Value Object

According to the above specification, the interface of an Interval Value Object looks like the following code:

— Note: The following code is written in C# language in such a way that the discussed idea can be conveyed only. The reader can rewrite it according to his needs, respecting the general characteristics of the IVO interface.

public interface IIntervalVariable<TSelf, TValueType> where TSelf : IIntervalVariable<TSelf, TValueType> where TValueType : INumber<TValueType>
{
protected TValueType Value { get; }
static abstract TSelf Zero { get; }
public static abstract TSelf operator ++(TSelf other);
public static abstract TSelf operator --(TSelf other);

public static abstract TSelf operator +(TSelf left);
public static abstract TSelf operator -(TSelf left);

public static abstract TSelf operator +(TSelf left, TSelf right);
public static abstract TSelf operator -(TSelf left, TSelf right);

public static abstract bool operator <(TSelf left, TSelf right);
public static abstract bool operator >(TSelf left, TSelf right);
public static abstract bool operator <=(TSelf left, TSelf right);
public static abstract bool operator >=(TSelf left, TSelf right);
}

A few notes about the IIntervalVariable interface:

  • Considering that two algebraic operations of addition and subtraction are possible on the interval variable, the operators ++, — , +, — have been implemented.
  • Given that Interval Value Objects have all the features of Ordinal Value Objects, they have the ability to implement <, >, =< and <= operators so that the “order” of values can be understood.

The following code snippet shows how to use Temperature class, which represents the temperature on the Celsius scale. (Remember that temperature in the Kelvin scale cannot be an Interval variable due to its true zero).

Assert.True(Temperature.Zero.Value == 0);

var t1 = new Temperature(4);
var t2 = new Temperature(40);

var t3 = t1 + t2;

Assert.True(t3.Value == 44);

var t4 = ++t3;
Assert.True(t4.Value == 45);

var t5 = -t2;
Assert.True(t5.Value == -40);

Assert.True(t1 < t2);
Assert.True(t3 <= new Temperature(45));

As you can see, when we model a concept like temperature (Celsius) as an Interval Value Object, we have complete control over the operators allowed for it. While if we have modeled the temperature as Double (Primitive data type in C#), there would be no such control, for example, multiplication/division operations on its values would be possible.

This idea aligns with the general idea of Make the Implicit Explicit. In other words, when we implicitly know that the operation of multiplication / division on the concept of temperature (Celsius) is meaningless, it is better to show it transparently in our model.

Ratio Value Object

And we come to the last group of variables which are called Ratio Value Objects. As mentioned in the second part, this variable, while having all the characteristics of the last three types of variables, has its own characteristic, that is, having an absolute or true zero.

As a result, all algebraic operations including addition, subtraction, multiplication and division can be performed on this variable. Like pounds and grams that have a true zero point. Birth rate, death rate, cholesterol level, commodity price, divorce rate, age, weight, height, population, number of overtime hours and etc., are all examples of applications of this scale.

Mathematical Features of Ratio Value Object

Another important point about this type of variable is that due to having real zero, the value of this variable can never become negative. For example, -1 meter length or -2kg weight are not meaningful.

Accordingly, the IRatioVariable interface has all operators of the IIntervalVariable interface, and in addition supports two multiplication and division operators. In fact, difference between mathematical features of IIntervalVariable and IRatioVariable interfaces is that the latter, supports . Therefore, the IRatioVariable interface is something like this:

public interface IRatioVariable<TSelf, TValueType> where TSelf : IRatioVariable<TSelf, TValueType> where TValueType : INumber<TValueType>
{
protected TValueType Value { get; }
static abstract TSelf Zero { get; }

public static abstract TSelf operator ++(TSelf other);
public static abstract TSelf operator --(TSelf other);

public static abstract TSelf operator +(TSelf left);
public static abstract TSelf operator -(TSelf left);
public static abstract TSelf operator +(TSelf left, TSelf right);
public static abstract TSelf operator -(TSelf left, TSelf right);

public static abstract bool operator <(TSelf left, TSelf right);
public static abstract bool operator >(TSelf left, TSelf right);
public static abstract bool operator <=(TSelf left, TSelf right);
public static abstract bool operator >=(TSelf left, TSelf right);

public static abstract bool operator /(TSelf left, TSelf right);
public static abstract bool operator *(TSelf left, TSelf right);
}

For example, pay attention to the Unit Test of the Population class, which represents the population variable:

var p1 = new Population(1_000_000);
var p2 = new Population(500_000);
Assert.True(p1 > p2);

var p3 = p1 + p2;
Assert.True(p3.Value == 1_500_000);

Assert.True((-p3).Value == 1_500_000);

Assert.True((p2 - p1) == Population.Zero);

Assert.True((p1 / p2).Value == 2);

Assert.True((p2 * 2).Value == p1.Value);

Assert.Throws<ArgumentOutOfRangeException>(() => new Population(-1));

A few notes about the Population class:

  1. Because the relative variable has absolute zero, the negation operator in this example has returned the positive value of the variable.
  2. For the same reason as above, when the larger value is subtracted from the smaller value (the population of 1 million is subtracted from the population of 500,000), the result is logically zero!
  3. According to the definition of a nominal variable, it is not possible to set its value less than zero. Because this variable has real zero.

In next example, you see that Capacity as Ratio Value Object how it has been used:

Assert.True(Capacity.Zero.Value == 0);

var c1 = new Capacity(1.2m);
var c2 = new Capacity(2.2m);

var c3 = c1 + c2;

Assert.True(c3.Value == 3.4m);
Assert.True(-c3.Value == -3.4m);

Assert.True(c1 < c2);
Assert.True(c2 > c1);

Assert.True(-c3 == -(c1 + c2));

var c4 = new Capacity(1.2m);
Assert.Equal(c1, c4);

Assert.True(c1 * 2 == new Capacity(2.4m));

Conclusion:

What has so far been introduced in books and educational resources about Value Object, although it provides valuable help in modeling and controlling complexity, but due to its generality, it can often confuse developers in the correct and useful implementation.

Considering this issue and inspired by the way statisticians categorize variables, I suggest that whenever it seems that a concept can be modeled as a Value Object, it is better to more carefully examine the characteristics of Variable, and try to model that concept as one of the following types of value objects:

  • Nominal Value Object
  • Ordinal Value Object
  • Interval Value Object
  • Ratio Value Object

Therefore, by adding these four new types of Value Object to the design and modeling toolbox and replacing it with the common concept of Value Object, not only richer and more understandable models can be created, but it will also force analysts and designers to think more and understand domain concepts more precisely.

In the last part, I will explain about the source code that contains the proposed implementation for the 4 new building blocks along with how to use them.

--

--