public class Complex {
private double real;
private double imag;
...
}
then the rep space R is the
set of Complex
objects, and the abstract space A
is the set of complex numbers./**Whether the type is mutable or immutable is a crucial property that should be mentioned in the class overview.
* Complex represents an immutable complex number. <=== this is A
*/
public class Complex {
private double real; <=== these fields form R
private double imag;
...
}
/**The r in AF(r) represents an element in the rep space. In other words, it's a reference to a Complex object. So we can refer to fields of the object r on the right-hand side of the abstraction function. (Incidentally, r stands for representation; the Liskov book names this variable c, for concrete, but it means the same thing.)
* Complex represents an immutable complex number.
*/
public class Complex {
private double real;
private double imag;
// The abstraction function is
// AF(r) = r.real + i * r.imag
...
}
/**(The square brackets and colons are sequence construction syntax. See the next section for more details.)
* Cons represents an immutable sequence of objects.
*/
public class Cons {
private Object car;
private Cons cdr;
// The abstraction function is
// AF(r) = [] if r = null
// [r.car] : AF(r.cdr) if r != null
...
}
/**This is simple and clear, but remember that it's just shorthand for AF(r).
* Complex represents an immutable complex number.
*/
public class Complex {
private double real;
private double imag;
// The abstraction function is
// real + i * imag
...
}
/**
* Line represents an immutable line segment in the plane.
* @specfield start : Point // start point
* @specfield end : Point // end point
*/
By convention, in specification fields, lowercase types like sequence or set refer to mathematical entities.
Capitalized types
refer to
other ADTs (i.e., Java classes). Where you have a choice, prefer
a mathematical entity as the type of a spec field; better to use
sequence than List, for example. It's more elegant, and reduces
the coupling between your specification and concrete Java classes.
public class Line {
private Point start;
private Point end;
// Abstraction function is
// AF(r) = line l such that
// l.start = r.start
// l.end = r.end
...
}
But this abstraction function is hardly worth writing down.
Here's a more interesting rep for the same ADT:public class Line {
private Point start;
private double length;
private double angle;
// Abstraction function is
// AF(r) = line l such that
// l.start = r.start
// l.end.x = r.start.x + r.length * cos(r.angle)
// l.end.y = r.start.y + r.length * sin(r.angle)
...
}
Note that x and y are spec fields of the Point type. It was
convenient to define the point end
in terms of its spec fields as well.// Abstraction function isKeep in mind that on the left-hand side, start refers to a spec field; on the right-hand side, start refers to a concrete field. (x and y always refer to spec fields here, because the rep of the Point type isn't visible to us.) Isn't there a danger of confusion between the two starts? Not really. We gave the spec field and the concrete field the same name for good reason -- because they are equated by the abstraction function. Do we really have to say start=start explicitly in the abstraction function? Probably not. If the spec field start and the rep field start weren't equated by the abstraction function, then we should have given them different names. Remember that your goal in these kinds of specifications is not formal communication with a machine, but clear and unambiguous communication with another human being. Names matter.
// start = start
// end.x = start.x + length * cos(angle)
// end.y = start.y + length * sin(angle))
/**The abstraction function then describes how to peel apart the index field into a suit and a value:
* Card represents an immutable playing card.
* @specfield suit : {Clubs,Diamonds,Hearts,Spades} // card suit
* @specfield value : {Ace,2,...,10,Jack,Queen,King} // card rank
*/
public class Card {
private int index;
// Abstraction function is
// suit = S(index div 13) where S(0)=Clubs, S(1)=Diamonds, S(2)=Hearts, S(3)=Spades
// value = V(index mod 13) where V(1)=Ace, V(2)=2, ..., V(10)=10,
// V(11)=Jack, V(12)=Queen, V(0)=King
// (div and mod refer to the integer division and remainder operations)
// for example, 3 => Three of Clubs
// 14 => Ace of Diamonds
...
}
This abstraction function maps each representation object to a pair (suit,value), but rather than
writing it as a single function, we've specified it as two separate
ones, one for each specification.In addition to their use in abstraction functions, spec fields are
also useful for writing specifications of the ADT's operations.
Here's a specification for a method on the Line class:
/**
* @requires start not equal to end
* @return angle in radians between this line segment and a horizontal ray starting at start
* and extending to the right. The angle is measured in a counter-clockwise direction.
*/
public double getAngle ();
Specifications may refer to
specification fields, but never to
representation fields. Rep fields depend
on a particular implementation, so we don't want to expose them in a
specification, which can be implemented in many different ways.
When you're writing specifications for operations, you may find it useful to define derived spec fields. A derived spec field is just a spec field that can be written in terms of other spec fields. In other words, it's a shorthand. We use the syntax @derivedfield name : type // description = definition to define a derived field. Like spec fields, this syntax appears in the class overview:
/**Because length is defined only in terms of the spec fields start and end, you don't have to define it in your abstraction function. In fact, a derived spec field may not be defined in terms of concrete fields. If you find you need to mention a concrete field in order to define a derived spec field, then it shouldn't be a derived spec field at all, and its definition shouldn't appear in the class overview. It should be an ordinary spec field instead, and its definition should appear in the abstraction function, visible only to the implementor or maintainer of the class. So it would be wrong to try to make end into a derived spec field, e.g.:
* Line represents an immutable line segment in the plane.
* @specfield start : Point // start point
* @specfield end : Point // end point
* @derivedfield length : real // length of segment = dist(start, end)
*/
/**If we added length and angle as spec fields, then this would be OK --- but the more rep fields you duplicate as spec fields, the less abstraction (detail-hiding) your class actually provides. Judge carefully whether a property is important to clients of the type before adding it as a spec field or derived spec field. Slavishly repeating all the rep fields as spec fields is almost always pointless, unless the class is a trivial ADT (which Liskov calls a record type, section 5.3.4).
* Line represents an immutable line segment in the plane.
* @specfield start : Point // start point
* // THIS IS WRONG: refers to rep fields length and angle
* @derivedfield end : Point // end point =
* end.x = start.x + length * cos(angle)
* end.y = start.x + length * cos(angle)
*/
/**
* @requires length > 0
* @return angle in radians between this line segment and a horizontal ray starting at start
* and extending to the right. The angle is measured in a counter-clockwise direction.
*/
public double getAngle ();
Simplifying specifications is in fact the only real purpose of
derived
spec
fields.
The presence of a specification field does not imply anything about the interface or implementation of the class. Although spec fields often correspond to observer methods, that's not always true. The interface might not provide any observers that query the spec field's state, so clients of the class might not have access to the information stored in the spec field. Likewise, the implementation might not actually have a concrete field of the spec field's type: that information may be computed from multiple concrete fields, or it might not be available at all. The point is that specification fields are a convenience for use in abstraction functions and specifications.
// Rep invariant isMore commonly, however, we just drop the RI(r) detail and simply write the rep invariant as a predicate that must be true of this:
// RI(r) = r.name != null && r.balance >= 0
// Rep invariant isA rep invariant may mix concrete Java syntax (rep field references, method calls, instanceof, !=, ==) with abstract mathematical syntax (sequence/set/tuple construction, for all, there exists, summation, =). A rep invariant may also be simple English, of course, as long as it is unambiguous. Here are some examples:
// name != null && balance >= 0
// for all i, transactions[i] instanceof TransIf the rep uses other ADTs, it may refer to them either by their spec fields or by their operations. For example, suppose the Trans type has a spec field amount that is accessible by the operation getAmount. Then the rep invariant for Account (which uses Trans objects) might look like this:
// suit in {Clubs,Diamonds,Hearts,Spades}
// string contains no duplicate characters
// size = number of non-null entries in array
(1) // balance == sum (for all i) of transactions[i].amountor this:
(2) // balance == sum (for all i) of transactions[i].getAmount()or even this (since transactions is an instance of an ADT with a get operation):
(3) // balance == sum (for all i) of transactions.get(i).getAmount()All these are equivalent. But it's important to keep in mind that amount in (1) above refers to a spec field in Trans, not to a concrete field. Unless Account and Trans are cooperating very closely, it isn't appropriate for Account to break the abstraction barrier and refer to the rep fields of Trans directly. By contrast, it's perfectly normal for Account's rep invariant to refer directly to Account's rep fields.
public class Card {
private int index;
// Abstraction function is
// suit = S(index div 13) where S(0)=Clubs, S(1)=Diamonds, S(2)=Hearts, S(3)=Spades
// value = V(index mod 13) where V(1)=Ace, V(2)=2, ..., V(10)=10, V(11)=Jack, V(12)=Queen,
// V(0)=King
...
}
In this case, the abstraction function isn't defined when the suit
number, index div 13, is
anything but 0, 1, 2, or 3. So that rules out values of index less than 0 or greater
than 51:// Rep invariant isMake sure you don't restrict the rep invariant so much that the abstraction function no longer covers the entire abstract value space A! If you do that, you'll no longer be implementing the abstract type, since some abstract values will be unrepresentable. Here, a little thought convinces us that index still represents 52 cards, all distinct.
// 0 <= index <= 51
/** CharSet represents a mutable set of characters. */This implementation of the remove method only works if there are no duplicates in the string, because it only deletes the first occurrence of c that it finds. Notice that the abstraction function is fully defined on R here -- it doesn't care whether or not there are duplicate characters, because the set construction syntax implicitly ignores them. But we need the rep invariant to ensure that remove always results in the correct abstract value, i.e. a set that does not contain c.
public class CharSet {
private StringBuffer chars;
// Abstraction function is
// { chars[i] | 0 <= i < chars.size }
...
/** @modifies this
* @effects this_post = this - {c}
*/
public void remove (char c) {
int i = chars.indexOf(Character.toString (c));
if (i != -1) chars.deleteCharAt (i);
}
}
// name != null && transactions != nullMake sure your constructors and producers actually establish the rep invariant, though. That means, if any of these fields are initialized from parameters, you have to check for null before you put them in the rep.
// for all i, transactions[i] instanceof TransJava 1.5 (coming soon) will improve this situation by allowing you to declare the type of the contained objects as a parameter to the collection's type:
// transactions is a list of Trans objects (less mathematical, probably clearer)
// studentsToSections maps Student to Integer
List<Trans> transactions; // this syntax works only in Java 1.5Once that's the case, you'll be able to leave this out of your rep invariant, because the Java runtime system will check it for you automatically. Until then, however, put it in your rep invariant; better safe than sorry.
Map<Student,Integer> studentsToSections; // only in Java 1.5
List/*<Trans>*/ transactions;That's helpful documentation, and forward thinking, but the compiler isn't paying any attention to it. So you still need to check the types in your rep invariant -- particularly checkRep, the exectable form of your rep invariant described in the next section.
Map/*<Student,Integer>*/ studentsToSections;
/** @effects: nothing if this satisfies rep invariant;
otherwise throws an exception
*/
private void checkRep () {
if (balance < 0)
throw new RuntimeException ("balance should be >= 0");
if (name == null)
throw new RuntimeException ("name should be non-null");
// for all j, sum (i=0..j) transactions[i].amount >= 0
int b = 0;
for (int i = 0; i < transactions.size(); i++) {
Trans t = (Trans) transactions.get(i);
b += t.getAmount();
if (b < 0)
throw new RuntimeException("balance went negative");
}
// balance = sum (for all i) transactions[i].amount
if (balance != b)
throw new RuntimeException ("balance should equal sum of transactions[i].amount");
}
The best place to put checkRep
in your class is right after your fields. You can either write
your rep invariant as a separate comment, or intersperse the
constraints of your rep invariant as exception messages in checkRep (as was done
above). The latter approach is probably better, because it makes
it more likely that the comment will be kept current with the code in checkRep. Any part of the rep
invariant that you can't write as executable code, just leave as a
comment.// using junit.framework.Assert (you don't have to be writing a unit test to use this class)Calls to checkRep should be placed at the start and end of every public mutator, at the end of every constructor. For an observer, the call to checkRep should be placed at the start, and at the end also if the observer changes the rep -- and even if it supposedly doesn't (since you may be wrong). Private methods generally don't call checkRep, because private methods may be called while the rep is in an intermediate state that doesn't satisfy the rep invariant.
Assert.assertTrue ("balance should be >= 0", balance >= 0);
// using Java 1.4 assert syntax
assert balance >= 0 : "balance should be >= 0";
/**
* Card represents an immutable playing card.
* @specfield suit : {Clubs,Diamonds,Hearts,Spades} // card suit
* @specfield value : {Ace,2,...,10,Jack,Queen,King} // card rank
*/
public class Card {
private int index;
// Abstraction function is
// suit = S(index div 13) where S(0)=Clubs, S(1)=Diamonds, S(2)=Hearts, S(3)=Spades
// value = V(index mod 13) where V(1)=Ace, V(2)=2, ..., V(10)=10, V(11)=Jack, V(12)=Queen,
// V(0)=King
// Rep invariant is
// 0 <= index <= 51
/**
* Check the rep invariant.
* @effects: nothing if this satisfies rep invariant;
* otherwise throws an exception
*/
private void checkRep() {
if (index < 0 || index > 51)
throw new RuntimeException ("card index out of range");
}
/**
* @effects makes a new playing card with given suit and value
*/
public Card(CardSuit suit, CardValue value) {
... // initialize Card
checkRep ();
}
/**
* @effects returns this.suit <=== "suit" refers to the spec field
*/
public CardSuit getSuit() {
checkRep ();
CardSuit s = ... // decode suit
checkRep ();
return s;
}
/**
* @effects returns this.value <=== "value" is the spec field
*/
public CardValue getValue() {
checkRep ();
CardValue v = ... // decode value
checkRep ();
return v;
}
...
}
The calls to checkRep() in all the observers are probably unnecessary
in this case, since the class is simple, immutable, and clearly has no
rep exposure.
They're included anyway to illustrate what you would want to do in a
more complex class.
// Abstraction function isA compiler would jump for joy at reading this (if the compiler were actually reading your abstraction function). A human would stare at it and curse.
// <[x,y],s^[for all 0<i<=chars.size(), chars[chars.size()-i]]> | x=front.cdr,
// y=back.car, s=n.toString()>
AF(r) = list l such that...
AF(r) = c_0 + c_1*x + c_2*x^2 + ...
where c_i = ...
suit = ...
value = ...
for example, 3 => Three of Clubs
reverse(back)
chars contains no duplicatesContrast this with the formal alternative:
for all 0<=i<j<chars.size, chars[i] != chars[j]