Classes and objects

Classes, objects, attributes, methods and constructors; encapsulation, access specifiers, getters and setters, and class diagrams.

A1.7Programming techniques and object-oriented programmingA level20 min

Do this lesson in the simulator

A procedural program keeps its data in variables and passes it to subroutines. That works, but nothing ties the robot's odometer total to the code that is allowed to change it: any subroutine could add to it, reset it, or set it to nonsense. Object-oriented programming (OOP) bundles data together with the subroutines that act on it, and hides the data so the only way to change it is through those subroutines.

Class, object, attribute, method

  • A class is a template, or blueprint, that defines what data an object holds and what it can do.
  • An object is one instance of a class, with its own values. Making one is called instantiation.
  • An attribute is a variable belonging to an object: its data, sometimes called its state.
  • A method is a subroutine defined in a class: what the object can do.
  • A constructor is the method that runs when an object is created, to give its attributes their starting values. In Python it is __init__.
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

class Horn:
    def __init__(self, note):         # the constructor
        self.note = note              # an attribute
        self.times = 0

    def sound(self):                  # a method
        tone(self.note, 0.2)
        self.times = self.times + 1

low = Horn(262)                       # instantiation: two objects of one class
high = Horn(784)
low.sound()
high.sound()
high.sound()
print("low sounded", low.times, "times; high sounded", high.times, "times")

Run this in the simulator

Horn is the class; low and high are two objects of it, each with its own note and its own times. Inside a method, self is the object the method was called on: in high.sound(), self is high, so only high.times goes up.

Encapsulation and information hiding

Encapsulation means keeping an object's attributes and the methods that use them together in one class. It goes with information hiding: making attributes private, so code outside the class cannot read or change them directly, and must use the class's public methods. The class then controls every change and can refuse bad ones.

Access is set by access specifiers:

Specifier Class diagram symbol Who can use it Python convention
Public + Any code a plain name: speed
Private - Only the class's own methods two underscores: __speed
Protected # The class and its subclasses one underscore: _speed

Python does not enforce these as strictly as Java or C#. A name starting with two underscores is renamed behind the scenes, so motor.__speed fails outside the class, which is close enough to private for our purposes. One underscore is only a convention that tells other programmers to keep out.

Methods that read or change a private attribute are called getters and setters, or accessor and mutator methods. A setter is where validation goes:

class Motor:
    def __init__(self):
        self.__speed = 0              # private

    def get_speed(self):              # getter
        return self.__speed

    def set_speed(self, speed):       # setter: the only way to change it
        if speed < 0 or speed > 100:
            raise ValueError("speed must be 0 to 100")
        self.__speed = speed

m = Motor()
m.set_speed(60)
print(m.get_speed())
try:
    m.set_speed(250)
except ValueError as error:
    print("refused:", error)
try:
    print(m.__speed)
except AttributeError:
    print("__speed is private")
print(m.get_speed())

Run this in the simulator

The advantages of encapsulation are that an object cannot be put into an invalid state from outside, that the inside of a class can be changed (storing the speed differently, say) without breaking any code that uses it, and that each class can be written and tested as a unit.

Class diagrams

A class diagram shows a class as a box in three parts: the name, the attributes, and the methods, each marked +, - or #. Types may be shown after a colon.

Class diagram for OdometerOdometer- name : String- total : Integer+ drive(cm : Integer)+ get_total() : Integer+ report() : String
Class diagram for the Odometer class in the task

In the exam languages

OCR's exam reference language writes a class with class ... endclass, marks attributes and methods public or private, calls the constructor new, and creates objects with the keyword new:

class Odometer
    private name
    private total

    public procedure new(givenName)
        name = givenName
        total = 0
    endprocedure

    public function getTotal()
        return total
    endfunction
endclass

tripA = new Odometer("trip A")
print(tripA.getTotal())

Task: the odometer class

Write the class Odometer from the class diagram.

  • The constructor __init__(self, name) stores name (a string) in the private attribute self.__name and sets the private attribute self.__total to 0.
  • drive(self, cm) takes a whole number of cm. If cm is 0 or more it drives forward cm; if it is negative it drives backward by -cm. Either way it adds the size of the move, abs(cm), to self.__total.
  • get_total(self) returns the total.
  • report(self) returns the string <name>: <total> cm.

Then make two objects, trip_a = Odometer("trip A") and trip_b = Odometer("trip B"), and call trip_a.drive(20), trip_b.drive(-10) and trip_a.drive(15) in that order. Print trip_a.report() then trip_b.report(), which should show trip A: 35 cm and trip B: 10 cm.

# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

class Odometer:
    def __init__(self, name):
        self.__name = name

trip_a = Odometer("trip A")

Challenges

  1. Add reset(self). Should it be public or private? Draw the new class diagram.
  2. Try print(trip_a.__total) outside the class. Then try print(trip_a._Odometer__total). What does that tell you about how private Python's privacy is?
  3. Add a setter for the name that refuses an empty string with a ValueError.