Module 1 Β· days 10–20 Β· cheatsheet

OOP via Analogies

The whole module on one page β€” analogy on the left of your memory, definition on the right. Print it (Ctrl/Cmd+P) and stick it above your desk.

1.Classes as BlueprintsThe architect's drawing

A class is a type definition: it declares the fields (state) and methods (behaviour) that its instances will have, plus how they are initialised. It allocates nothing on its own; instantiation allocates memory and binds the fields to that instance..

class House:
    def __init__(self, colour):
        self.colour = colour

    def open_door(self):
        return "creak"

# No house exists yet β€” this is only the drawing.
2.Objects as HousesBuilt from the drawing

An object is an instance: a distinct allocation with its own field values, referenced by an identity. Mutating one instance's state does not affect another instance of the same class; only shared/static members are common to all..

a = House("white")
b = House("white")

a.colour = "ochre"

print(a.colour)  # "ochre"
print(b.colour)  # "white"  <- untouched
print(a is b)    # False β€” two objects
3.Attributes vs MethodsWhat it has vs what it does

Attributes (fields/properties) hold state; methods are functions bound to the instance that read or mutate that state. Methods receive the instance implicitly (this/self), which is how they reach the fields without being handed them..

class House:
    def __init__(self):
        self.colour = "white"     # HAS
        self.lights_on = False    # HAS

    def switch_lights(self):      # DOES
        self.lights_on = not self.lights_on

    def repaint(self, colour):    # DOES
        self.colour = colour
4.ConstructorsThe build crew's checklist

The constructor runs at instantiation to establish the object's invariants: assign required fields, validate arguments, acquire resources. Code that depends on a fully-initialised object should never run before the constructor returns..

class House:
    def __init__(self, colour, address):
        if not colour:
            raise ValueError("a house needs a colour")
        self.colour = colour
        self.address = address
        self.ready = True      # invariants now hold

# __init__ runs once, at creation. Never again.
5.EncapsulationWalls and a doorbell

Encapsulation restricts direct access to internal state and exposes a controlled interface. Private fields let the class enforce invariants and change its implementation without breaking callers, since callers only depend on the public surface..

class House:
    def __init__(self):
        self.__safe_code = "4821"   # name-mangled

    def ring_bell(self):            # public
        return "who is it?"

h = House()
h.ring_bell()      # fine
h.__safe_code      # AttributeError
6.Inheritance as GeneticsChildren of the blueprint

A subclass inherits the fields and methods of its superclass and may extend them. This models an is-a relationship; the subclass can be used anywhere the superclass is expected (the Liskov substitution principle)..

class House:
    def open_door(self):
        return "creak"

class Townhouse(House):   # inherits everything
    pass

class Cottage(House):
    pass

Townhouse().open_door()   # "creak" β€” for free
7.Method OverridingThe child who cooks differently

Overriding replaces the superclass implementation of a method with the subclass's own, keeping the signature identical. Dispatch is dynamic: the runtime picks the implementation from the object's actual type, not the declared type..

class House:
    def open_door(self):  return "creak"

class Townhouse(House):
    def open_door(self):  return "buzz"

h: House = Townhouse()
h.open_door()      # "buzz"
# resolved by the OBJECT, not the annotation
8.Polymorphism as a Multi-toolOne handle, many heads

Polymorphism lets one interface serve many concrete types. Callers program against the abstraction, so adding a new implementation requires no change at the call site β€” the core mechanism behind extensible designs..

class Dog:
    def make_sound(self): return "woof"
class Cat:
    def make_sound(self): return "meow"
class Car:
    def make_sound(self): return "honk"

for thing in (Dog(), Cat(), Car()):
    print(thing.make_sound())
# the loop never checks a type
9.AbstractionThe steering wheel

Abstraction exposes essential behaviour and hides implementation detail, usually via an interface or abstract base. It reduces coupling: the consumer depends on a contract, so the detail behind it can be swapped freely..

def steer(angle):
    _column_rotate(angle)      # you never call these

def _column_rotate(a):
    _rack_translate(a * 0.6)

def _rack_translate(mm):
    _hydraulics_assist(mm)

steer(-15)   # the only line you write
10.Composition over InheritanceLego, not a family tree

Composition builds behaviour by holding collaborators (has-a) rather than inheriting it (is-a). It avoids deep, rigid hierarchies and the fragile-base-class problem, and allows behaviour to be swapped at runtime by injecting a different collaborator..

class Car:
    def __init__(self, engine):
        self.engine = engine      # HAS-A

    def swap_engine(self, engine):
        self.engine = engine      # at runtime

car = Car(PetrolEngine())
car.swap_engine(ElectricEngine())