Class Design, Hiding Attributes

CS 65: Introduction to Computer Science I

Reviewing Objects and Classes

Let's continue with the Rectangle class we worked with last time:

In [1]:
class Rectangle:
    """
    Used for representing rectangles
    
    attributes: length, width
    """
    def __init__(self, starting_length, starting_width):
        self.length = starting_length
        self.width = starting_width
        
    def area(self):
        return self.length*self.width
    
    def perimeter(self):
        return 2*self.length + 2*self.width
    

rec1 = Rectangle(7,5) 
rec2 = Rectangle(60,95)
print("Rectangle 1's area:",rec1.area() )
print("Rectangle 2's area:",rec2.area() )
Rectangle 1's area: 35
Rectangle 2's area: 5700

Group Discussion

In the example above, which things are

  1. Classes
  2. Objects
  3. Attributes
  4. Methods

Also discuss the following:

  1. Each of the methods has a parameter self, but we don't put something inside the parentheses when we call it. What is self and where does it get its value from?
  2. When does the __init__ method get called? Where do its parameters starting_length and starting_width come from?

Group Class Design Exercise

Suppose you need to create a Car class so you can have a data type for representing cars in your program. Discuss the following:

  1. What are some attributes a car might have? (i.e., what are some data/variables that are important for cars?)
  2. What are some methods a car might have? (i.e., what kinds of actions/operations might a program need to do with a car?)

Then, create a Car class that includes at least 3 attributes and one method.

Create at least two objects that are instances of the Car class (i.e., their type is Car) - maybe represent each of your cars.

In [ ]:
 

Another Example

Consider the following BankAccount class.

In [2]:
class BankAccount:
    """
    A class for creating objects representing bank accounts
    
    attributes:
        balance - amount of money in the account
        customer_name - the customer's name
        interest_rate - interest rate for the account
    """
    def __init__(self, starting_customer_name):
        self.customer_name = starting_customer_name
        self.balance = 0
        self.interest_rate = 0.0
        
    def deposit(self, amount):
        self.balance += amount
    
    def apply_interest(self):
        self.balance = self.balance * (1+self.interest_rate)
        
    def display_info(self):
        print("Account Holder:",self.customer_name)
        print("Balance:",self.balance)
        print("Interest Rate:",self.interest_rate)
        
erics_checking = BankAccount("Eric")
erics_checking.deposit(500) #this gets passed to the amount parameter
erics_checking.interest_rate = 0.01
erics_checking.apply_interest()
erics_checking.display_info()
Account Holder: Eric
Balance: 505.0
Interest Rate: 0.01

Classes can control how attributes are used

What if a programmer tries to use an attribute in a way that doesn't make sense?

In [3]:
als_savings = BankAccount("Al Yankovic")
als_savings.deposit(-100)
als_savings.display_info()
Account Holder: Al Yankovic
Balance: -100
Interest Rate: 0.0

Let's fix this up

Hiding attributes

It's bad manners to directly mess with an object's attributes. But...

A programmer could bypass your updated deposit method and do something weird like

In [6]:
als_savings.balance = "SPAM!"
als_savings.display_info()
Account Holder: Al Yankovic
Balance: SPAM!
Interest Rate: 0.0

Fortunately, you can protect against this.

Attribute names that start with two underscore are hidden and can't be changed in this way outside the class's code

In [3]:
class BankAccount:
    """
    A class for creating objects representing bank accounts
    
    attributes:
        balance - amount of money in the account
        customer_name - the customer's name
        interest_rate - interest rate for the account
    """
    def __init__(self, starting_customer_name):
        self.customer_name = starting_customer_name
        self.__balance = 0
        self.interest_rate = 0.0
        
    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
        else:
            print("Error: the deposit amount must be positive.")
    
    def apply_interest(self):
        self._balance = self.__balance * (1+self.interest_rate)
        
    def display_info(self):
        print("Account Holder:",self.customer_name)
        print("Balance:",self.__balance)
        print("Interest Rate:",self.interest_rate)
        
als_savings = BankAccount("Al Yankovic")
als_savings.__balance = "SPAM!" #doesn't actually change self.__balance
als_savings.display_info()
Account Holder: Al Yankovic
Balance: 0
Interest Rate: 0.0

Why is this useful?

You, as the designer of the class, get to have complete control over how the data is used and manipulated.

  • Fewer errors from misuse
  • Easier to maintain