1. Customizing functionality via inheritance
Great job so far! In the previous video, you learned that inheritance allows us to build relationships between classes.
2. Hierarchy
For example, a SavingsAccount is a special kind of BankAccount, known as a child class, that has all the functionality of the parent class BankAccount, but also contains additional properties like interest rate and a method to compute interest.
3. What we have so far
We know how to create an empty child class, now let's customize it by
4. Customizing constructors
adding a constructor for SavingsAccount.
The SavingsAccount constructor takes two arguments, balance, just like BankAccount, and an additional interest_rate argument.
In the constructor, we first call the BankAccount, or parent, class's init method.
We use BankAccount-dot-init to tell Python to call the constructor from the parent class. We pass self to that constructor, which, in this case, refers to a SavingsAccount object.
Remember that in Python, objects of a child class are also considered as objects of the parent class.
Then, we can add more functionality, in this case, initializing the interest rate.
We aren't required to call the parent constructor in the subclass or to call it first, but we'll likely almost always use the parent constructor.
5. Create objects with a customized constructor
Let's create a SavingsAccount object. We provide a balance and interest rate, and can access these attributes as usual.
6. Adding functionality
We can add methods to a child class in the same way we add them to any other class.
Within the methods we add, we can use data from both the child and parent classes.
For example, here, we add a compute_interest method that returns the amount of interest in the account.
We include the n_periods argument. The method performs a calculation using n_periods, the interest_rate attribute from the SavingsAccount class, which is the child class, along with using self to access the balance attribute from the BankAccount class, which is the parent.
7. Customizing functionality
SavingsAccount inherits the withdraw method from the parent BankAccount class, so calling withdraw on a savings object will execute the same code as calling it on a BankAccount object.
We want to create a CheckingAccount class, which should have a slightly modified version of the withdraw method: it will have another argument and adjust the withdrawal amount.
8. Adding a second child class
To create CheckingAccount, we add a code that executes the parent class BankAccount constructor. We follow this with a new deposit method, and a withdraw method, but we add a new argument to withdraw - a fee that represents the additional withdrawal fee.
We compare the amount to the withdrawal limit from the constructor, and, if the condition is met, call the parent's withdraw method, subtracting the amount and the fee from the balance. If the amount is above the limit, the withdrawal is declined.
So this method runs almost the same code as the BankAccount's withdraw method without re-implementing it - just augmenting.
9. Comparison
Now when we call withdraw from a CheckingAccount object, the new customized version will be used. But when we call it from regular BankAccount, the parent class version will be used.
The interface of the call is the same, and the actual method that is called is determined by the instance class, which is an application of polymorphism.
Another difference is that for a CheckingAccount object, we could call the method with 2 parameters.
But trying this call for a BankAccount object would cause an error because the method defined in the BankAccount class was not affected by the changes in the subclass.
It's worth noting that our approach technically violates polymorphism because the parent and child class no longer have a unified interface - their methods have different arguments, so they do not work in the same way.
10. Let's practice!
Now, let's go customize some methods!