Philip Guo (Phil Guo, Philip J. Guo, Philip Jia Guo, pgbovine)

The benefits of object-oriented programming using class invariants (a.k.a. representation invariants)

Programming with class invariants (a.k.a. representation invariants) is a great idea ...

  • because you can be confident that your objects are always presented to their clients in a consistent state

  • A class (representation) invariant is a condition that all objects of that class must satisfy while it can be observable by clients

    • it is defined by invariants over all fields (data members) of the class
    • e.g., a Child class may contain fields like age, birthdate, social_security_number. One possible class invariant (in pseudocode) is:
(0 <= age < 18) and
(birthdate + age == todays_date) and
isLegalSSN(social_security_number)

which means, in English, that a child's age must be non-negative and less than 18 and must be accurately reflected by his/her birthdate, and that he/she must have a valid social security number as defined by the isLegalSSN() function.

  • Programmers often informally write class invariants in documentation or as code comments

    • Note that they shouldn't be written in public APIs since the representation should be private (hidden from clients) so that it can be freely changed
  • However, when possible, codify these invariants in a checkRep() method

    • checkRep() should contain a bunch of assert statements, one for each class (representation) invariant. For example:
class Child {

  ...

  private void checkRep() {
    assert (0 <= age < 18);
    assert (birthdate + age == todays_date);
    assert (isLegalSSN(social_security_number));
  }
}
  • ... and call checkRep() at the beginning and end of every public method
    • The principle here is that clients should only be able to access your object via public methods, so ...
      • at the beginning of a method, check to make sure the representation is intact before it starts operating on its internal state (fields)
      • at the end of a method, check to make sure the representation is intact before it hands over control back to the client (caller)
    • For example, look at the changeSSN() method:
class Child {
  ...

  public void changeSSN(int newSSN) {
    checkRep();
    ... method body ...
    checkRep();
  }

  private void checkRep() {
    assert (0 <= age < 18);
    assert (birthdate + age == todays_date);
    assert (isLegalSSN(social_security_number));
  }
}
  • Call checkRep() at the end of all constructors so that you can ensure that the client receives an object that is well-formed

  • checkRep() is like assert statements for object-oriented programming

    • in fact, it operates by the same principle as asserts
    • allows your program to fail earlier when a problem arises and data becomes corrupted
  • Get in the habit of augmenting your checkRep() function as your class changes over time

    • Be as stringent as possible in checking representation invariants
    • If performance is an issue, you can selectively disable checkRep() by wrapping them in if statements with DEBUG variable guards
  • Note that you don't need to call checkRep() from your private methods since the representation can be temporarily broken while your object has control of execution

    • In a single-threaded program, no client can observe the state of an object while its own methods are executing
    • In a multi-threaded program, though, things get more complicated, so you might need to synchronize or lock objects

Related articles

Created: 2008-06-20
Last modified: 2008-06-20
Related pages tagged as programming: