B3.1 Fundamentals of OOP
A single class: design, code, static members, encapsulation. Examples in Python.
B3.1.1: Classes, Objects and Evaluating OOP
| Term | Definition |
|---|---|
| Class | A blueprint (template) that defines the attributes (instance variables) and behaviours (methods) of an object. |
| Object | An instance of a class: it exists in memory and its instance variables have values. |
| Instantiation | Creating an object by calling the constructor and giving starting values. Memory is allocated and the object gets an identifier. |
| Constructor | Special method that creates the object and sets its starting values. Python: __init__. |
| Attribute | Data stored about the object (instance variable). |
| Behaviour | What 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
| Feature | Meaning |
|---|---|
| Encapsulation | Data and methods wrapped in one class, data hidden and accessed only through methods. |
| Inheritance | A 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
| Advantages | Disadvantages |
|---|---|
| Models real world entities naturally | Too much overhead for small, simple programs |
| Breaks big problems into manageable classes (decomposition) | Larger programs, can run slower |
| Code reuse: classes, libraries, inheritance | Harder for beginners to follow: logic is spread across many classes |
| Developers can work on different classes at the same time | Needs 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.
| Symbol | Access |
|---|---|
- | private |
+ | public |
# | protected |
| underline | static |
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 to | Each object | The class |
| Copies | One per object, values differ | One copy, shared by all objects |
| Accessed by | Object name: one.getTitle() | Class name: Book.getBooksBorrowed() |
| Exists | Once the object is created | From the start of the program, even with no objects |
| Example | a book's title | total 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
| Part | Meaning |
|---|---|
__init__ | Constructor. Runs on instantiation, sets starting values. |
self | The 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 notation | object.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.
| Access | Python | Java | Class | Subclass | Package | Other classes |
|---|---|---|---|---|---|---|
| Private | self.__cost | private | Yes | No | No | No |
| Protected | self._cost | protected | Yes | Yes | Yes | No |
| Public | self.cost | public | Yes | Yes | Yes | Yes |
The textbook swaps these. In Python two underscores = private, one = protected (by convention). Public variables mean the code is not encapsulated.
- Accessor (getter): returns a private value. Mutator (setter): changes it, and can validate first.
- Features of encapsulated code: private attributes, public getters and setters, data only changed through methods, private helper methods.
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
a1 = Article("Top 10 creepy movies", "Patrick D", 350)
Article a1 = new Article("Top 10 creepy movies", "Patrick D", 350);- Class = blueprint. Object = instance in memory. Instantiation = calling the constructor.
- UML: three boxes (name, attributes, methods).
-private,+public,#protected, underline static. - Static = one shared copy, belongs to the class. Non-static = one per object.
- Encapsulation = private data + public getters and setters.
- Python private is
__, protected is_. - Searching returns -1 for "not found", so check
!= -1, never> 0.