B3.1 Fundamentals of OOP

A single class: design, code, static members, encapsulation. Examples in Python.

Contents

B3.1.1: Classes, Objects and Evaluating OOP

TermDefinition
ClassA blueprint (template) that defines the attributes (instance variables) and behaviours (methods) of an object.
ObjectAn instance of a class: it exists in memory and its instance variables have values.
InstantiationCreating an object by calling the constructor and giving starting values. Memory is allocated and the object gets an identifier.
ConstructorSpecial method that creates the object and sets its starting values. Python: __init__.
AttributeData stored about the object (instance variable).
BehaviourWhat the object can do (method).

Why classes: storing 2 birds needs 8 separate variables or 4 parallel lists. With a Bird class, each bird is one object holding all its own data, and all birds go in one list.

OOP features

FeatureMeaning
EncapsulationData and methods wrapped in one class, data hidden and accessed only through methods.
InheritanceA subclass gets the variables and methods of a superclass and adds its own. Common code written once. (B3.2, HL)
Polymorphism"Many forms": the same method name behaves differently depending on the object or the parameters. (B3.2, HL)

Advantages and disadvantages

AdvantagesDisadvantages
Models real world entities naturallyToo much overhead for small, simple programs
Breaks big problems into manageable classes (decomposition)Larger programs, can run slower
Code reuse: classes, libraries, inheritanceHarder for beginners to follow: logic is spread across many classes
Developers can work on different classes at the same timeNeeds careful design up front
Encapsulation keeps data secure and code easy to maintain

Suitable? Yes for systems with many interacting entities (passenger records, games). Not worth it for a short one off script (an automated server task).

B3.1.2: UML Class Diagrams

A UML class diagram is a standard diagram of a class that any developer can read. Attributes become instance variables, behaviours become methods.

Dish - name: String - costPrice: double - retailPrice: double - dishCount: int + Dish(name, costPrice) + getName(): String + setCostPrice(c: double): void + getRetailPrice(): double - calcRetail(): double # getMargin(): double ← class name ← attributes: name: type ← methods(parameters): return type ← underlined = static
SymbolAccess
-private
+public
#protected
underlinestatic

Normal design: attributes private, methods public, helper methods private. void means the method returns nothing (typical for setters).

Other UML diagrams (not drawn in this course): sequence, use case, state, package.

B3.1.3: Static vs Non-static

Non-static (instance)Static (class)
Belongs toEach objectThe class
CopiesOne per object, values differOne copy, shared by all objects
Accessed byObject name: one.getTitle()Class name: Book.getBooksBorrowed()
ExistsOnce the object is createdFrom the start of the program, even with no objects
Examplea book's titletotal books borrowed from the library

Static methods can only use static variables directly, because they have no object (no self). In Python, a static variable is declared in the class body, outside __init__. Java uses the static keyword.

class Book:
    booksBorrowed = 0                        # STATIC: one copy, shared by all books

    def __init__(self, title):
        self.__title = title                 # NON-STATIC: each book has its own
        self.__borrowed = False

    def borrow(self):
        self.__borrowed = True
        Book.booksBorrowed += 1              # accessed through the CLASS name

    @staticmethod
    def getBooksBorrowed():                  # static method: no self
        return Book.booksBorrowed


one = Book("Community and Support")
two = Book("Where is Archibald?")
three = Book("Horticulture for the Balcony")
one.borrow()
two.borrow()
print(Book.getBooksBorrowed())
2

B3.1.4: Defining Classes and Instantiating Objects

class Plant:
    def __init__(self, name, family, distribution, bloom):   # constructor
        self.__name = name                  # private instance variables
        self.__family = family
        self.__distribution = distribution
        self.__bloom = bloom

    def getName(self):                      # accessor (getter)
        return self.__name

    def getBloom(self):
        return self.__bloom

    def setBloom(self, b):                  # mutator (setter)
        self.__bloom = b

    def __str__(self):                      # text version of the object
        return self.__name + " (" + self.__family + ")"


fav = Plant("Narcissus", "Amaryllidaceae", "Worldwide", True)   # instantiation
print(fav)                 # calls __str__
print(fav.getName())       # dot notation
fav.setBloom(False)
print(fav.getBloom())
Narcissus (Amaryllidaceae)
Narcissus
False
PartMeaning
__init__Constructor. Runs on instantiation, sets starting values.
selfThe current object. self.__name is the object's variable, name is the parameter. (Java uses this.)
__str__Returns a text version of the object. Called by print(). (Java: toString().)
fav = Plant(...)Instantiation. fav is the identifier, the values are actual parameters.
Dot notationobject.method() calls a method on that object.

Classes usually go in their own file and are imported into a runner file: from Plant import Plant.

B3.1.5: Encapsulation and Information Hiding

Encapsulation: bundling data and the methods that act on it inside one class, with the data hidden (private) and accessed only through public methods. Information hiding: other code can't see or change the internal data directly.

AccessPythonJavaClassSubclassPackageOther classes
Privateself.__costprivateYesNoNoNo
Protectedself._costprotectedYesYesYesNo
Publicself.costpublicYesYesYesYes

The textbook swaps these. In Python two underscores = private, one = protected (by convention). Public variables mean the code is not encapsulated.

class Dish:
    def __init__(self, name, costPrice):
        self.__name = name
        self.__costPrice = costPrice
        self.__retailPrice = self.__calcRetail()

    def __calcRetail(self):                  # private method: hidden markup
        return self.__costPrice * 1.6

    def setCostPrice(self, c):
        if c > 0:                            # setter can validate
            self.__costPrice = c
            self.__retailPrice = self.__calcRetail()

    def getRetailPrice(self):
        return self.__retailPrice


d = Dish("Spaghetti Carbonara", 8.50)
d.setCostPrice(10)
print(d.getRetailPrice())
d.setCostPrice(-5)                           # rejected
print(d.getRetailPrice())
print(d.__costPrice)                         # can't reach private data
16.0
16.0
AttributeError: 'Dish' object has no attribute '__costPrice'. Did you mean: 'setCostPrice'?
Advantages of encapsulation
Security: private data can't be changed by outside code.
Validation: setters reject bad values, so data stays accurate.
Maintenance: the inside of a class can change without breaking code that uses it.
Reuse: a self contained class can be dropped into other programs.
Abstraction: users only need the public methods, not how they work (like a library).

Objects in Lists

Same list and loop skills as B2, but you reach the data through the object's methods with dot notation.

class Game:
    def __init__(self, title, age, minutes):
        self.__title = title
        self.__age = age
        self.__minutes = minutes

    def getTitle(self):
        return self.__title

    def getAge(self):
        return self.__age

    def __str__(self):
        return self.__title + ", age " + str(self.__age) + "+"


games = []
games.append(Game("Chicken vs the Egg", 12, 120))
games.append(Game("Foxes and Hedgehogs", 14, 180))
games.append(Game("Cards against History", 18, 60))
games.append(Game("Better or Worse", 5, 20))
del games[2]                                  # remove by index

age = 13
for g in games:                               # selection with objects
    if g.getAge() <= age:
        print(g)
Chicken vs the Egg, age 12+
Better or Worse, age 5+
# linear search by attribute (games list from above)
target = "Better or Worse"
index = -1
for i in range(len(games)):
    if games[i].getTitle() == target:
        index = i
if index != -1:                               # NOT index > 0: index 0 is valid
    print("Found at", index)
else:
    print("Not found")

# bubble sort by title
n = len(games)
for i in range(n - 1):
    for j in range(n - 1 - i):
        if games[j].getTitle() > games[j + 1].getTitle():
            games[j], games[j + 1] = games[j + 1], games[j]
for g in games:
    print(g.getTitle())
Found at 2
Better or Worse
Chicken vs the Egg
Foxes and Hedgehogs

Comparing strings: Python uses > and ==. Java needs .equals() and .compareTo() (returns 0 if equal, <0 if first comes before, >0 if after).

Textbook bugs to avoid: its searches use if index > 0, which misses a match at index 0 (use != -1). Its Python bubble sort loop ranges are wrong, and its binary search goes the wrong way.

Practice Questions

Too much overhead for small programs [1], as designing classes takes more time and code than a simple script [1]. Programs are larger [1], which can make them slower to run and harder for beginners to follow [1].
Article - title: String - subtitle: String - author: String - words: int + getTitle(): String + getSubtitle(): String + getAuthor(): String + getWords(): int + setTitle(t: String): void + setWords(w: int): void
Marks: class name [1], all four attributes with types [1], private attributes and public methods shown with - and + [1], suitable getters and setters with return types [1].
A static variable belongs to the class, a non-static variable belongs to each object [1]. There is only one copy of a static variable, shared by all objects, while each object has its own copy of a non-static variable [1]. A static variable is accessed through the class name, e.g. a library's total books borrowed, while a non-static one is accessed through an object, e.g. a book's title [1].
a1 = Article("Top 10 creepy movies", "Patrick D", 350)
Marks: identifier and assignment [1], class name called as the constructor [1], arguments in the right order with correct types: strings in quotes, 350 as an integer [1]. Java: Article a1 = new Article("Top 10 creepy movies", "Patrick D", 350);
Security [1]: private data can only be changed through the class's own methods, which can validate it [1]. Easier maintenance [1]: the internal code can be changed in one place without affecting other classes that use it [1].
Instance variables are private [1]. Public accessor (getter) methods to read them [1]. Public mutator (setter) methods to change them, often with validation [1].
Ninja Notes: