Discussion 7: Object-Oriented Programming

This is an online worksheet that you can work on during discussions. Your work is not graded and you do not need to submit anything.

OOP

Object-oriented programming (OOP) is a programming paradigm that allows us to treat data as objects, like we do in real life.

For example, consider the class Student. Each of you as individuals is an instance of this class.

Details that all CS 61A students have, such as name, are called instance variables. Every student has these variables, but their values differ from student to student. A variable that is shared among all instances of Student is known as a class variable. For example, the extension_days attribute is a class variable as it is a property of all students.

All students are able to do homework, attend lecture, and go to office hours. When functions belong to a specific object, they are called methods. In this case, these actions would be methods of Student objects.

Here is a recap of what we discussed above:

  • class: a template for creating objects
  • instance: a single object created from a class
  • instance variable: a data attribute of an object, specific to an instance
  • class variable: a data attribute of an object, shared by all instances of a class
  • method: a bound function that may be called on all instances of a class

Instance variables, class variables, and methods are all considered attributes of an object.

Q1: WWPD: Student OOP

Below we have defined the classes Professor and Student, implementing some of what was described above. Remember that Python passes the self argument implicitly to methods when calling the method directly on an object.

class Student:

    extension_days = 3 # this is a class variable

    def __init__(self, name, staff):
        self.name = name # this is an instance variable
        self.understanding = 0
        staff.add_student(self)
        print("Added", self.name)

    def visit_office_hours(self, staff):
        staff.assist(self)
        print("Thanks, " + staff.name)

class Professor:

    def __init__(self, name):
        self.name = name
        self.students = {}

    def add_student(self, student):
        self.students[student.name] = student

    def assist(self, student):
        student.understanding += 1

    def grant_more_extension_days(self, student, days):
        student.extension_days = days

What will the following lines output?

>>> callahan = Professor("Callahan")
>>> elle = Student("Elle", callahan)
>>> elle.visit_office_hours(callahan)
>>> elle.visit_office_hours(Professor("Paulette"))
>>> elle.understanding
>>> [name for name in callahan.students]
>>> x = Student("Vivian", Professor("Stromwell")).name
>>> x
>>> [name for name in callahan.students]
>>> elle.extension_days
>>> callahan.grant_more_extension_days(elle, 7)
>>> elle.extension_days
>>> Student.extension_days

Q2: Email

We would like to write three different classes (Server, Client, and Email) to simulate a system for sending and receiving emails. A Server has a dictionary mapping client names to Client objects, and can both send Emails to Clients in the Server and register new Clients. A Client can both compose emails (which first creates a new Email object and then sends it to the recipient client through the server) and receive an email (which places an email into the client's inbox).

Emails will only be sent/received within the same server, so clients will always use the server they're registered in to send emails to other clients that are registered in the same rerver.

An example flow: A Client object (Client 1) composes an Email object with message "hello" with recipient Client 2, which the Server routes to Client 2's inbox.

Email example

To solve this problem, we'll split the section into two halves (students on the left and students on the right):

  • Everyone will implement the Email class together
  • The first half (left) will implement the Server class
  • The other half (right) will implement the Client class

Fill in the definitions below to finish the implementation!

Run in 61A Code
Run in 61A Code
Run in 61A Code

Q3: Keyboard

We'd like to create a Keyboard class that takes in an arbitrary number of Buttons and stores these Buttons in a dictionary. The keys in the dictionary will be ints that represent the position on the Keyboard, and the values will be the respective Button. Fill out the methods in the Keyboard class according to each description, using the doctests as a reference for the behavior of a Keyboard.

Hint: You can iterate over *args as if it were a list.

Run in 61A Code

Q4: Relay

In a Math Olympiad style relay, team members solve questions while sitting in a line. Each team member's answer is calculated based on the answer from the team member sitting in front of them.

For example, suppose we have three team members, adder, adder2, and multiplier, with adder sitting at the very front, adder2 in the middle, and multiplier at the end. When we call the relay_calculate method from multiplier, we first apply the adder operation to the input x. Then, the answer from adder is passed into the adder2 operation. Finally, the answer from adder2 is passed into the multiplier operation. The answer from multiplier is our final answer.

Relay example

Additionally, each team member has a relay_history method, which uses the fact that each team member has an instance variable history. relay_history returns a list of the answers given by each team member, and this is updated each time we call relay_calculate.

Here are some examples of how the TeamMember class should behave:

>>> adder = TeamMember(lambda x: x + 1) # team member at front 
>>> adder2 = TeamMember(lambda x: x + 2, adder) # team member 2
>>> multiplier = TeamMember(lambda x: x * 5, adder2) # team member 3
>>> adder.relay_history() # relay history starts off as empty
[]
>>> adder.relay_calculate(5) # 5 + 1
6
>>> adder2.relay_calculate(5) # (5 + 1) + 2
8
>>> multiplier.relay_calculate(5) # (((5 + 1) + 2) * 5)
40
>>> multiplier.relay_history() # history of answers from the most recent relay multiplier participated in
[6, 8, 40]
>>> adder.relay_history()
[6]
>>> multiplier.relay_calculate(4) # (((4 + 1) + 2) * 5)
35
>>> multiplier.relay_history()
[5, 7, 35]
>>> adder.relay_history() # adder participated most recently in multiplier.relay_calculate(4), where it gave the answer 5
[5]
>>> adder.relay_calculate(1)
2
>>> adder.relay_history() # adder participated most recently in adder.relay_calculate(1), where it gave the answer 2
[2]
>>> multiplier.relay_history() # but the most relay multiplier participated in is still multiplier.relay_calculate(4)
[5, 7, 35]

Fill in the definitions below to complete the implementation of the TeamMember class!

Run in 61A Code

Class Methods

Now we'll try out another feature of Python classes: class methods. A method can be turned into a class method by adding the classmethod decorator. Then, instead of receiving the instance as the first argument (self), the method will receive the class itself (cls).

Class methods are commonly used to create "factory methods": methods whose job is to construct and return a new instance of the class.

For example, we can add a robo_factory class method to our Dog class that makes robo-dogs:

class Dog:
    def __init__(self, name, owner):
        self.name = name
        self.owner = owner
    @classmethod
    def robo_factory(cls, owner):
        return cls("RoboDog", owner)

    # With other previously defined methods not written out

Then a call to Dog.robo_factory('Sally') would return a new Dog instance with the name "RoboDog" and owner "Sally".

Note that with the call above, we don't have to explicitly pass in the Dog class as the cls argument, since Python implicitly does that for us. We only have to pass in a value for owner. When the body of the Dog.robo_factory is run, the line cls("RoboDog", owner) is equivalent to Dog("RoboDog", owner) (since cls is bound to the Dog class), which creates the new Dog instance.

Q5: Own A Cat

Now implement the cat_creator method below, which takes in a string owner and creates a Cat named "[owner]'s Cat", where [owner] is replaced with the name in the owner string.

Hint: To place an apostrophe within a string, the entire string must be surrounded in double-quotes (i.e. "DeNero's Dog")

Run in 61A Code