We previously presented class specification as a means of getting a big picture of what a class does. Clients of the class may not need to understand the implementation of the class, nor how this implementation relates to the specification. Still, documenting the links between specification and implementation may result in invaluable help during maintenance and evolution.

In this post, we show how to document the implementation of a class, and how to map the representation value of this class to its abstract value.

Representation Value

A representation value (or “rep value”) of a class consists of the value of the instance variables, which themselves are the constituent variables of the representation. It is thus the implementation of the abstract value. As a running example, consider a class Complex that acts as an ADT for complex numbers. A possible implementation for it is the following:

/**
 * Complex represents an immutable complex number, e.g., 3 + 2i.
 */
class Complex {

    private double real;
    private double imag;

    // ...
}

In the above, we represent a complex number by two doubles: one for the real part of the number and the other for the coefficient of the imaginary part of it. These are the instance variables. An alternative would have been to represent the real part as a double and the imaginary part as a specific ADT for imaginary numbers. Both implementations map to the same abstract value, which is the mathematical concept of Complex number.

Mapping representation to abstraction

Maintenance can be greatly facilitated by explaining how to “translate” the rep value into the abstract value. This documentation takes the form of an Abstraction Function (AF). It typically describes how the abstract value (often documented by its spec fields or some mathematical concept) is derived from the instance variables. This comes down to explaining how a concrete object of the class represents the abstract concept modeled by the ADT.

Formally, the AF is a function from R to A, where R is the rep value space, and A the abstract value space. An element of R is an object, while an element of A is abstract and has no existence inside the computer. In our Complex example, R is the set of all possible instances of the class (whose value is defined by the instance variables real and imag), while A is the set of complex numbers and has thus no existence within the computer memory.

We propose to document abstraction functions using a functional notation, AF(r), where r represents an element in the rep value space. In the Complex example, we simply write how to obtain the complex number from the instance variables of a given instance of the Complex class.

/**
 * Complex represents an immutable complex number.
 */
class Complex {

    private double real;
    private double imag;

    /*
     * Abstraction Function:
     *   AF(r) = r.real + i * r.imag
     */

    // ...
}

Non-trivial abstraction function

The mapping between abstraction and representation is not always as obvious as for the Complex class. Let us consider the Segment class from our previous post, whose abstract value is defined by two Points. A natural way to implement this class is to make use of a Point class that represents the mathematical concept of Cartesian Point. Yet other approaches exist and may be used for various reasons, e.g. performance or to facilitate the implementation of specific methods.

/**
 * Segment represents an immutable line segment on a Cartesian plane.
 *
 * @specfield startPoint : Point // The starting point of the segment.
 * @specfield endPoint : Point   // The ending point of the segment.
 *
 * @derivedfield length : real   // length = sqrt((startPoint.x - endPoint.x)^2
 *                                            + (startPoint.y - endPoint.y)^2)
 *                                  The straight line distance.
 *
 * @invariant length > 0
 */
class Segment {

    private int startX;
    private int startY;
    private double length;
    private double angle;

    // ...
}

In the above, the representation of Segment relies on two integers and two doubles to represent the coordinates of the starting point, the length of the segment, and the angle formed by the segment and the abscissa axis.

Basically, the spec field startPoint is represented by the coordinates startX and startY, while the spec field endPoint, or rather its coordinates are derived from the coordinates of the starting point, the length and the angle. Let us see how to document this mapping as an abstraction function.

/**
 * Segment represents an immutable line segment on a Cartesian plane.
 *
 * ...
 */
class Segment {

    private int startX;
    private int startY;
    private double length;
    private double angle;

    /*
     * Abstraction Function:
     *   AF(r) = Segment s such that
     *     s.startPoint = (r.startX, r.startY)
     *     s.endPoint.x = r.startX + r.length * cos(r.angle)
     *     s.endPoint.y = r.startY + r.length * sin(r.angle)
     */

    // ...
}

This AF clearly illustrates that the mapping between the representation and the spec fields is not always immediate. Here, we have to document how the spec fields are derived from the instance variables.

This way of documenting effectively shows how the ADT is internally represented. This is valuable for any developer that will work on the implementation of the class one day or another, as it greatly facilitates its understanding of the subtleties of the implementation.

 

Have you ever met cases where the implementation of a class is drastically different from the abstract concept it represents? How do you document such cases?