Designing object-oriented programs
Let’s talk about some principles of designing classes.
Is-A vs. Has-A
Let’s say we’re designing an ecology simulator. We have a collection of classes:
- Animal
- Tiger
- Mongoose
- Habitat
What might the relationships among these classes be?
We know that tigers and mongooses are animals, so it would make sense for
them to both be subclasses of Animal
. A tiger isn’t a mongoose and a
mongoose isn’t a tiger; so, neither should be a subclass of the other.
Should Animal
be a subclass of Habitat
? Many animals live inside a
Habitat
. But, an animal is not in itself a habitat (except, perhaps, in
cases like remoras living on a whale). This case illustrates an important
distinction:
- Is-A
- If every A is a B, then perhaps A should be a subclass of B. For
instance, every convertible is a car, so
Convertible
could be a subclass ofCar
. - Has-A
- Is an A has a B or contains a collection of B, then A should
have a field of type B (or a list of B, etc.). For instance, a car has an
engine, so a
Car
class could have a fieldengine
whose type isEngine
.
Another way to think about this is to think about what inheritance does:
inheritance lets us share functionality between classes. An Animal
shouldn’t have the functionality of a Habitat
.
A Library class
We can add a Library
class to our little library system:
class Library: def __init__(self): self.shelves = [] def add(self, item: LibraryItem): self.shelves.append(item) def search(self, query: str) -> list: items = [] for item in self.shelves: if item.matches(query): items.append(item) return items
Should Library
inherit from LibraryItem
? How about vice versa?
Neither class should be a subclass of the other: a LibraryItem
is not a
Library
and a Library
is not a LibraryItem
.
Exercise: astronomy simulation
Imagine we were writing an astronomy simulation. What might the relationships be among these classes? Consider both has-A and is-A relationships.
- Star
- Planet
- Gas giant
- Galaxy
- Spiral Galaxy
- Earth
See the lecture capture for details.
Inheritance vs. instantiation
There’s a last, really important distinction to be made here. Let’s say we have something like this:
class Planet: pass #other methods elided class GasGiant(Planet): pass earth = Planet() # maybe need to pass size, climate type, etc...
GasGiant
is a subclass of Planet
. Both are classes. earth
is an
instance of Planet
–an object whose class is Planet
.
When to use dataclasses
At the beginning of the class, we used dataclasses
in order to represent
complex data types. We’ve also seen regular Python classes, which can also
be used for the same thing. When would we use each?
Dataclasses are very useful to represent data that won’t change during the program’s execution. For instance, if we wanted to represent people in our library system (authors of books or directors of movies, say) we might have a dataclass with fields for name and birth year:
from dataclasses import dataclass @dataclass class Person: name: str birth_year: int
Using dataclasses for data like this has some advantages. You get a nice display for free, and you get equality checking that works the way you want:
>>> Person("Doug", 1989) == Person("Doug", 1989) True