1 Invariants
- The most important, property of a good abstract data type is that it preserves its own invariants
- An invariant is a property of a program that is always true, for every possible runtime state of the program.
1.1 Immutability
Imutability is an example of invariants.
- Avoid directly access the fields(private, final in java). That is called representation exposure , meaning that code outside the class can modify the representation directly.
- In general, you should carefully inspect the argument types and return types of all your ADT operations. If any of the types are mutable, make sure your implementation doesn’t return direct references to its representation. Doing that creates rep exposure.
- An even better solution is to prefer immutable types. For example, we had used an immutable date object, like
java.time.ZonedDateTime
, instead of the mutablejava.util.Date
.
1.1.1 Immutable Wrappers Around Mutable Data Types
- The Java collections classes offer an interesting compromise: immutable wrappers.
- The downside here is that you get immutability at runtime, but not at compile time.
2 Rep Invariant and Abstraction Function
- The space of representation values (or rep values for short) consists of the values of the actual implementation entities.
- The space of abstract values consists of the values that the type is designed to support.
We can describe the relationship between these two space by give two things:
An abstraction function that maps rep values to the abstract values they represent:
AF:R→A
- Every abstract value is mapped to by some rep value.
- Some abstract values are mapped to by more than one rep value.
- Not all rep values are mapped.
A rep invariant that maps rep values to booleans:
RI:R→boolean
- For a rep value r , RI(r) is true if and only if r is mapped by AF . In other words, RI tells us whether a given rep value is well-formed.
The essential point is that designing an abstract type means not only choosing the two spaces – the abstract value space for the specification and the rep value space for the implementation – but also deciding what rep values to use and how to interpret them. For example:
public class CharSet {
private String s;
// Rep invariant:
// s.length is even
// s[0] <= s[1] <= ... <= s[s.length()-1]
// Abstraction Function:
// represents the union of the ranges
// {s[i]...s[i+1]} for each adjacent pair of characters
// in s
...
}
2.1 Checking the Rep Invariant
If your implementation asserts the rep invariant at run time, then you can catch bugs early. Here’s an example tests rep invariant that all input should not be null:
// Check that the rep invariant is true
// *** Warning: this does nothing unless you turn on assertion checking
// by passing -enableassertions to Java
private void checkRep() {
assert s.length() % 2 == 0;
...
}
You should certainly call checkRep() to assert the rep invariant at the end of every operation that creates or mutates the rep – in other words, creators, producers, and mutators. As a defensive practice, it should be called at every operation of your ADT.
2.2 Documenting the AF, RI, and Safety from Rep Exposure
you should write a rep exposure safety argument . This is a comment that examines each part of the rep, looks at the code that handles that part of the rep (particularly with respect to parameters and return values from clients, because that is where rep exposure occurs), and presents a reason why the code doesn’t expose the rep. Here is an example:
// Immutable type representing a tweet.
public class Tweet {
private final String author;
private final String text;
private final Date timestamp;
// Rep invariant:
// author is a Twitter username (a nonempty string of letters, digits, underscores)
// text.length <= 140
// Abstraction Function:
// represents a tweet posted by author, with content text, at time timestamp
// Safety from rep exposure:
// All fields are private;
// author and text are Strings, so are guaranteed immutable;
// timestamp is a mutable Date, so Tweet() constructor and getTimestamp()
// make defensive copies to avoid sharing the rep's Date object with clients.
// Operations (specs and method bodies omitted to save space)
public Tweet(String author, String text, Date timestamp) { ... }
public String getAuthor() { ... }
public String getText() { ... }
public Date getTimestamp() { ... }
}
2.3 How to Establish Invariants
The full rule for proving invariants is: Structural induction. If an invariant of an abstract data type is:
- Established by creators and producers.
- Preserved by mutators, and observers.
- No representation exposure occurs.
then the invariant is true of all instances of the abstract data type.
3 ADT invariants replace preconditions
/**
* @param set1 is a sorted set of characters with no repeats
* @param set2 is likewise
* @return characters that appear in one set but not the other,
* in sorted order with no repeats
*/
static String exclusiveOr(String set1, String set2);`
We can instead use an ADT that captures the desired property:
/** @return characters that appear in one set but not the other */
static SortedSet<Character> exclusiveOr(SortedSet<Character> set1, SortedSet<Character> set2);
Reference
[1] 6.005 — Software Construction on MIT OpenCourseWare | OCW 6.005 Homepage at https://ocw.mit.edu/ans7870/6/6.005/s16/