Chapter 7: Object Oriented Programming

Chapter 7: Object Oriented Programming

Procedural Programming: writing programs made of functions that perform similar tasks

  • Procedures typically operate on data items that are separate from the procedures
  • Data items commonly passed from one procedure to another
  • Focus: to create procedures that operate on program’s data

Object-Oriented Programming: focused on creating objects

  • Objects = entity that contains data and procedures
  • Data attributes = data
  • Methods = procedures that perform operations on the data attributes

Encapsulation: combining data (attributes) and procedures (methods) into a single object

  • OOP allows for ~, single object = single class in OOP
  • Ensures that the private properties (attributes) can only be accessed / altered (modified) using public methods
  • VS procedural programming where attributes and methods are all separate entities
  • Main purpose: data integrity (protects integrity of the data)
  • Restricts direct access to attributes of an object, so users cannot access state values for all of the variables of a particular object

Data hiding: object’s data attributes are hidden from code outside the object and access is restricted to the object’s methods

  • Protects from accidental corruption
  • Outside code does not need to know internal structure of the object

Object

  • Data attributes: define the state of an object
  • E.g. clock object has second, minute, and hourdata attributes
  • Public methods: allow external code to manipulate object
  • E.g. set_time, set_alarm_time
  • Private methods: used for object’s inner workings

Class vs Instance/Object

  • Class: code that specifies the data attributes and method of a particular type of object
  • Similar to blueprint of a house or a cookie cutter
  • Instance: an object created from a class
  • Similar to a specific house build according to the blueprint, or a specific cookie
  • There can be many instances of one class
  • E.g. housefly and mosquito objects are instances of the Insect class

Class definitions

  • Set of statements that define a class’s methods and data attributes
  • Begin with class Class_names:
  • Class names often start with uppercase letter
  • Method definition like any other python function definition
  • self parameter: required in every method in the class
    ⇒ references the specific object that the method is working on
  • Initializer method (i.e. class’s constructor)
  • Automatically executed when an instance of the class is created
  • Initializes object’s data attributes and assigns self parameter to object
  • def __init__(self):
  • Usually the first method in a class definition
import random
# The Coin class simulates a coin that can be flipped.
class Coin:
# initializes the sideup data attribute with 'Heads'.
def __init__(self):
self.sideup = 'Heads'
# The toss method generates a random number in the range of 0 to 1.
# If the number is 0, then sideup is set to 'Heads'. Otherwise, sideup is set to 'Tails'
def toss(self):
if random.randint(0,1) == 0:
self.sideup = 'Heads'
else: self.sideup = 'Tails'
# The get_sideup method returns the value referenced by sideup
def get_sideup(self):
return self.sideup
  • Creating an Instance of a class
  • Call the initializer method
    My_instance = Class_Name()
  • Use dot notation to call any of the class methods using the created instance
    My_instance.method()
  • Because the self parameter references the specific instance of the object, the method will affect this instance, reference to self is passed automatically
# The main function.
def main():
# Create an object from the Coin class
my_coin = Coin()
# Display the side of the coin that is facing up
print('This side is up:', my_coin.get_sideup())
for count in range(10):
my_coin.toss()
print(my_coin.get_sideup())
# Call the main function.
main()

Actions caused by the Coin() expression

The my_coin variable references a Coin object

Data Hiding

  • Hiding Attributes
  • An object’s data attributes should be private
  • To denote private attribute or method: name prefixed with an underscore (e.g. _sideup)
  • Accessor and Mutator Methods
  • All of a class’s data attributes are private and provide methods to access and change them
  • Accessor / Getter methods: return a value from a class’s attribute without changing it ⇒ safe way for code outside the class to retrieve value of attributes
  • Mutator / Setter methods: store or change value of a data attribute
  • Storing classes in modules
  • Filename for module must end in .py
  • Module can be imported to programs that use the class

Class methods can have multiple parameters in addition to self

  • For __init__, parameters needed to create an instance of the class
  • E.g. a BankAccount object is created with a balance
  • When called, the initializer method receives a value to be assigned to the _balance attribute
  • For other methods, parameters needed to perform required task
  • E.g. deposit method amount to be deposited

E.g. the BankAccount Class

account.py

# The BankAccount class simulates a bank account
class BankAccount:
# The __init__ method accepts an argument for the account’s balance.
# It is assigned to the _balance attribute
def __init__(self, bal):
self._balance = bal
# The deposit method makes a deposit into the account
def deposit(self, amount):
self._balance += amount
# The withdraw method withdraws an amount from the account
def withdraw(self, amount):
if self._balance >= amount:
self._balance -= amount
else:
print(‘Error: insufficient funds’)
return
# The get_balance method returns the account balance
def get_balance(self):
return self._balance

account_test.py

# This program demonstrates the BankAccount class
import account
def main():
# Get starting balance
start_bal = float(input(‘Enter your starting balance: ’))
# Create a BankAccount object
savings = account.BankAccount(start_bal)
# Deposit user’s paycheck
pay = float(input(‘How much were you paid this week?’))
print(“I will deposit that into your account.”)
savings.deposit(pay)
# Display balance
print(f‘Your account balance is ${savings.get_balance():.2f}’)
# Get amount to withdraw
cash = int(input(‘How much would you like to withdraw?’)
print(‘I will withdraw that from your account’)
savings.withdraw(cash)
# Display balance
print(f‘Your account balance is ${savings.get_balance():.2f’)
# Call main function
main()
 
 

The __str__ method

  • Object’s state = value of object’s attribute at a given moment
  • Displays object’s state
  • Automatically called when you pass the object’s name to the print statement
  • Automatically called when the object is passed as an argument to the str function
# the __str__ method returns a string indicating the object’s state
def __str__(self):
state_string = f‘Your account balance is ${self._balance:.2f}.’
return state_string
# display balance
print(savings)

Working with instances

  • Instance attribute: belongs to a specific instance of a class
  • Created when a method uses a self parameter to create an attribute
  • If many instances of a class are created, each would have its own set of attributes

simulation.py

import random
# The Coin class simulates a coin that can be flipped
class Coin:
# The __init__ method initializes the _sideup data attribute with ‘Heads’.
def __init__(self):
self._sideup = ‘Heads’
# The toss method generates a random number in the range of 0 to 1
# If the number is 0, then sideup is set to ‘Heads’. Otherwise, sideup is set to ‘Tails’
def toss(self):
if random.randint(0,1) == 0:
self._sideup = ‘Heads’
else:
self._sideup = ‘Tails’
# The get_sideup method returns the value referenced by sideup
def get_sideup(self):
return self._sideup

coin_demo.py

# This program imports the simulation module and creates 3 instances of the Coin class
import simulation
def main():
# Create 3 objects from the Coin class
coin1 = simulation.Coin()
coin2 = simulation.Coin()
coin3 = simulation.Coin()
# Display the side of each coin that is facing up
print(‘I have 3 coins with these sides up: ’)
print(coin1.get_sideup())
print(coin2.get_sideup())
print(coin3.get_sideup())
print()
# Toss the coins
print(‘I am tossing all 3 coins…’)
print()
coin1.toss()
coin2.toss()
coin3.toss()
# Display the side of each coin that is facing up
print(‘Now here are the sides that are up: ’)
print(coin1.get_sideup())
print(coin2.get_sideup())
print(coin3.get_sideup())
print()
# Call the main function
main()

The coin1, coin2, and coin3 variables reference 3 Coin objects

The objects after the toss method

Passing objects as arguments

  • Methods and functions often need to accept objects as arguments
  • When you pass an object as an argument, you are actually passing a reference to the object. The receiving method or function has access to the actual object
  • Methods of the object can be called within the receiving function or method, and data attributes may be changed using mutator methods

Techniques for designing classes

  • Class diagram is a standard diagram for graphically depicting object-oriented systems
  • General layout: box divided into 3 sections:
  • Top section: name of class
  • Middle section: list of data attributes
  • Bottom section: list of class methods
  • Access Modifier:
    • private
    • public
  • E.g.
Class diagram for the Coin class | Class diagram for the Customer Class
Coin | Customer
-sideup | -name
+toss() | -address
+get_sideup() | -phone
+set_name()
+set_address()
+set_phone()
+get_name()
+get_address()
+get_phone()

Defining a simple class

  • Think about the behaviour and attributes of objects of new class
  • Choose an appropriate class name and develop a short list of the methods available to users
  • Write a short script that appears to use the new class in an appropriate way
  • Choose appropriate data structures for attributes
  • Fill in class template with __init__ and __str__
  • Complete and test remaining methods incrementally
  • Document your code

Inheritance

  • In reality, many objects are a specialised version of more general objects
  • E.g. grasshoppers and bees are specialized types of insects, have unique characteristics
  • “Is a” relationship: exists when one object is a specialized version of another object
  • Specialized object has all the characteristics of the general object + unique characteristics
  • E.g. rectangle is a shape, car is a vehicle
  • Inheritance**:** used to create an “is a” relationship between classes
  • Attributes & methods from the superclass are used as a basis for the subclass
  • Superclass (base class): a general class
  • Subclass (derived class): a specialized class, an extended version of the superclass
  • Inherits attributes and methods of the superclass
  • New attributes and methods can be added

E.g. need to create classes for cars, pickup trucks, and SUVs
All are automobiles
Have a make, year model, mileage, and price
This can be the attributes for the base case
In addition:
Car has a number of doors
Pickup truck has a drive type
SUV has a passenger capacity

In a class definition for a subclass:
To indicate inheritance, the superclass name is placed in parentheses after subclass name, eg. class Car(Automobile):
The initializer method of a subclass calls the initializer method of the superclass and then initializes the unique data attributes
Add method definitions for unique methods

vehicles.py

# The Automobile class holds general data about an automobile
class Automobile:
# The __init__ method accepts arguments for the make, model, mileage, and price. It initializes the data attributes with these values
def __init__(self, make, model, mileage, price):
self._make = make
self._model = model
self._mileage = mileage
self._price = price
# The following methods are mutators for the class’s data attributes
def set_make(self, make):
self._make = make
def set._model(self, model):
self._model = model
def set._mileage(self, mileage):
self._mileage = mileage
def set_price(self, price):
self._price = price
# The following methods are the accessors for the class’s data attributes
def get_make(self):
return self._make
def get_model(self):
return self._model
def get_mileage(self):
return self._mileage
def get_price(self):
return self._price
# The Car class represents a car. It is a subclass of the Automobile class.
class Car(Automobile):
# The __init__ method accepts arguments for the car’s make, model, mileage, price, and doors
# Note: init method is only required if there is any change to the attribute settings
def __init__(self, make, model, mileage, price, doors):
# call the superclass’s __init__ method & pass the required arguments (i.e. inherit attributes from superclass)
super().__init__(make, model, mileage, price)
# initialise the __doors attribute (new attribute)
self._doors = doors
# the set_doors method is the mutator for the __doors attribute
def set_doors(self, doors):
self._doors = doors
# the get_donors method is the accessor for the __doors attribute
def get_doors(self):
return self._doors
# The Truck class represents a pickup truck. It is the subclass of the Automobile class
class Truck(Automobile):
# The __init__ method accepts arguments for the car’s make, model, mileage, price, and drive type
def __init__(self, make, model, mileage, price, drive_type):
# Call the superclass’s __init__ method and pass the required arguments
super().__init__(make, model, mileage, price)
# Initialize the _drive_type attribute
self._drive_type = drive_type
# The set_drive_type method is the mutator for the _drive_type attribute
def set_drive_type(self, drive_type):
self._drive_type = drive_type
# The get_drive_type method is the accessor for the _drive_type attribute
def get_drive_type(self):
return self._drive_type
# The SUV class represents a sports utility vehicle. It is a subclass of the Automobile class
class SUV(Automobile):
# The __init__ method accepts arguments for the car’s make, model, mileage, price, and passenger capacity
def __init__(self, make, model, mileage, price, pass_cap):
# Call the superclass’s __init__ method and pass the required arguments
super().__init__(make, model, mileage, price)
# Initialize the _pass_cap attribute
self._pass_cap = pass_cap
# The set_pass_cap method is the mutator for the _pass_cap attribute
def set_pass_cap(self, pass_cap):
self._pass_cap = pass_cap
# The get_pass_cap method is the accessor for the _pass_cap attribute
def get_pass_cap(self):
return self._pass_cap

car_truck_suv_demo.py

# This program creates a Car object, a Truck object, and an SUV object
import vehicles
def main():
# Create a Car object for a used 2001 BMW with 70,000 miles, priced at $15,000, with 4 doors (create subclass object)
car = vehicles.Car(‘BMW’, 2001, 70000, 15000.0, 4)
# Create a Truck object for a used 2002 Toyota pickup with 40,000 miles, priced at $12,000, with 4-wheel drive
truck = vehicles.Truck(‘Toyota’, 2002, 40000, 12000.0, ‘4WD’)
# Create an SUV object for a used 2000 Volvo with 30,000 miles, priced at $18,500, with 5 passenger capacity
suv = vehicles.SUV(‘Volvo’, 2000, 30000, 18500.0, 5)
print(‘USED CAR INVENTORY’)
print(‘===================’)
# Display the car’s data
print(‘The following car is in inventory:’)
print(‘Make:’, car.get_make())
print(Model:’, car.get_model())
print(‘Mileage:’, car.get_mileage())
print(‘Price:’, car.get_price())
print(‘Number of doors:’, car.get_doors())
print()
# Display truck’s data
print(‘The following truck is in inventory:’)
print(‘Make:’, truck.get_make())
print(Model:’, truck.get_model())
print(‘Mileage:’, truck.get_mileage())
print(‘Price:’, truck.get_price())
print(‘Drive type:’, truck.get_drive_type())
print()
# Display SUV’s data
print(‘The following SUV is in inventory:’)
print(‘Make:’, suv.get_make())
print(Model:’, suv.get_model())
print(‘Mileage:’, suv.get_mileage())
print(‘Price:’, suv.get_price())
print(‘Passenger Capacity:’, suv.get_pass_cap())
print()
# Call the main function
main()
  • Inheritance in class diagram
  • In Class diagram, show inheritance by drawing a line with an open arrowhead from subclass to superclass
  • Polymorphism: an object’s ability to take different forms
  • Methods in the subclasses have the same name as methods in the superclass but behave differently
  • Essential ingredients of polymorphic behaviour:
  • Ability to define a method in a superclass and override it in a subclass
  • Subclass defines method with the same name
  • Ability to call the correct version of overridden method depending on the type of object that is used to call it
  • In previous inheritance examples showed how to override the __init__ method
  • Called superclass __init__ method and then added onto that
  • The same can be done for any other method. The method can call the superclass equivalent and add to it, or do something completely different

animals.py

# The Mammal class represents a generic mammal
class Mammal:
# The __init__ method accepts an argument for the mammal’s species
def __init__(self, species):
self._species = species
# The show_species method displays a message indicating the mammal’s species
def show_species(self):
print(‘I am a’, self._species)
# The make_sound method is the mammal’s way of making a generic sound
def make_sound(self):
print(‘Grrrrr’)
# The Dog class is a subclass of the Mammal class
class Dog(Mammal):
# The __init__ method calls the superclass’s __init__ method passing ‘Dog’ as the species
def __init__(self):
super().__init__(‘Dog’)
# The make_sound method overrides the superclass’s make_sound method
def make_sound(self):
print(‘Woof!’)
# The Cat class is a subclass of the Mammal class
class Cat(Mammal):
# The __init__ method calls the superclass’s __init__method passing ‘Cat’ as the species
def __init__(self):
super().__init__(‘Cat’)
#The make_sound method overrides the superclass’s make_sound method
def make_sound(self):
print(‘Meow’)

# Code that creates a Mammal object, a Dog object, and calls the methods:
>>> import animals
>>> mammal = animals.Mammal(‘regular mammal’)
>>> mammal.show_species()
I am a regular mammal
>>> mammal.make_sound()
Grrrrr
>>> dog = animals.Dog()
>>> dog.show_species()
I am a Dog
>>> dog.make_sound()
Woof!

Mutator ⇒ set

Accessor ⇒ get

self._balance ⇒ _ is to show it is a private attribute, but it’s optional??

super().__init__(…) ⇒ inheritance

Comments from the Word document

# Code that creates a Mammal object, a Dog object, and calls the methods:
>>> import animals
>>> mammal = animals.Mammal(‘regular mammal’)
>>> mammal.show_species()
I am a regular mammal
>>> mammal.make_sound()
Grrrrr
>>> dog = animals.Dog()
>>> dog.show_species()
I am a Dog
>>> dog.make_sound()
Woof!

Mutator ⇒ set

Accessor ⇒ get

self._balance ⇒ _ is to show it is a private attribute, but it’s optional??

super().__init__(…) ⇒ inheritance

Comments from the Word document