Class design¶
Learning outcomes
- Understand what an invariant is
- Write a class that protects an invariant
For teachers
Prior:
- What are classes?
- When to use classes, or when not?
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.
Example¶
Here we see some code where a user constantly checks if his/her stays positive:
positive_number = 42
assert positive_number >= 0
positive_number = do_something_with_it(positive_number)
assert positive_number >= 0
positive_number = do_something_else_with_it(positive_number)
assert positive_number >= 0
Wouldn't it be great if positive_number itself could check
if it is positive, instead of us asserting this at every step?
For that, we could write a class for exactly that,
with a name such as PositiveNumber.
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 to
lowest.
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:
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.
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:
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: your own class
Come up with a class you may need yourself and try to write it.
Work in src/learners
- Write the class that protects its invariant
Answer for a positive number
Answer for range
References¶
[Booch, 2008]Booch, Grady, et al. "Object-oriented analysis and design with applications." ACM SIGSOFT software engineering notes 33.5 (2008): 29-29.- Python classes
- C++ Core Guidelines
[CppCore C.2]C.2: Use class if the class has an invariant; use struct if the data members can vary independently https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-struct[CppCore C.8]C.8: Use class rather than struct if any member is non-public https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c8-use-class-rather-than-struct-if-any-member-is-non-public[Gamma et al., 1995]Gamma, Erich, et al. "Elements of reusable object-oriented software." Design Patterns (1995).