Skip to content

Class design

Learning objectives

  • Understand what an invariant is
  • Write a class that protects an invariant
For teachers

Prerequisites are:

  • .

Teaching goals are:

  • .

Prior:

  • .

Lesson plans:

gantt
  title Lesson plan pair programming 1
  dateFormat X
  axisFormat %s
  Introduction : intro, 0, 5s
  Theory 1: theory_1, after intro, 5s
  Exercise 1: crit, exercise_1, after theory_1, 15s
  Feedback 1: feedback_1, after exercise_1, 5s
  Theory 2: theory_2, after feedback_1, 5s
  Exercise 2: crit, exercise_2, after theory_2, 10s
  Exercise 2 after the break: crit, after exercise_2, 5s

Why?

You are modeling something in the real world as code. You want to use the same world in your code as in the real world and you want it to be natural to use in your code.

This is a goal of class design.

Benefits from object-oriented development

Benefits from object-oriented development (from [Booch, 2008]):

  • Appeals to the working of human cognition
  • Leads to systems that are more resilient to change
  • Encourages the reuse of software components
  • Reduces development risk
  • Exploits the expressive power of object-oriented programming languages

An invariant

An invariant is something that must always be. Some examples:

  • a persons' age must always be positive
  • a persons' total height must be longer than a persons' arms' length

Use a class if the class has an invariant [CppCore C.2].

For example, here we have a class with an invariant:

classDiagram
  class Range{
    -lowest
    -highest
  }
What is the invariant in the Range class?

The invariant is that highest~ must be bigger or equal tolowest`.

Writing a good class

A good class guarantees that its stored data is valid. For example, the class DnaSequence is probably a string of one or more A, C, G and T

  • the quality requirements for a function, among others a good interface
  • writing a design, documentation and tests all help

General class anatomy

  • A constructor: all data needed to create it
  • Private member variables
  • Public member functions
Prefer R?

Class anatomy in R:

  • R has four class types (S3, S4, R5, R6)
  • S3 classes are closest to structures
  • R6 classes are real classes

A DnaSequence class

Here is how to implement a class for a DNA sequence:

class DnaSequence:
    def __init__(self, sequence):
        assert is_dna_string(sequence)
        self._sequence = sequence

    def get_str(self):
        return self._sequence

a = DnaSequence("ACGT")
assert a.get_str() == "ACGT"

The init method (also known as a constructor) checks if the input is indeed a valid DNA string, using an assert. After that, the sequences is stored inside of the class, in a member variable called _sequence. The underscore signals (by social convention) that the value must be treated as 'do not touch' and that the only class itself will keep it valid.

However, nothing stops you from doing this:

a._sequence = "XXX" # No! Breaks the invariant!
assert a.get_str() == "XXX"

On the other hand, a Python developer can at least see that this convention was broken.

Note that some other programming languages completely disallows you from modifying a so-called 'private' member variable.

Inheritance and polymorphism quote

C++ is a horrible language. It's made more horrible by the fact that a lot of substandard programmers use it, to the point where it's much much easier to generate total and utter crap with it.

Linus Torvalds, 2007-09-06

Inheritance and polymorphism are easy to abuse and hard to use correctly [Gamma et al., 1995]. However, tried and known-to work combinations of classes called 'Design Patterns' do use inheritance and are known to work well [Gamma et al., 1995]

Gamma et al., 1995

[Gamma et al., 1995]

Exercise

Exercise: write a class with an invariant

  • Pick a class at your skill level:
Easiest: a class for a positive number

Here is an example how to use it:

x = PositiveNumber(3)
assert x.get_value() == 3
PositiveNumber(-1) # Must raise an exception

Work in src/learners.

Medium: a class for a range, e.g 'a range from 3 to 10'

Here is an example how to use it:

x = Range(3, 10)
assert x.get_lowest() == 3
assert x.get_highest() == 10
Range(100, 10) # Must raise an exception

Work in src/learners

Hard: a class in the learners' project

Work in src/bacsim

Don't break the main branch :-)

  • Write the class that protects its invariant
Answer for a positive number
class PositiveNumber:
    def __init__(self, any_positive_number):
        assert any_positive_number >= 0
        self._value = any_positive_number
    def get_value(self):
        return self._value
Answer for range
class Range:
    def __init__(self, any_lowest, any_highest):
        assert any_lowest <= any_highest
        self._lowest = any_lowest
        self._highest = any_highest
    def get_lowest(self):
        return self._lowest
    def get_highest(self):
        return self._highest

References