Principle
- Don’t repeat yourself; use existing implementations.
- Attributes that have been overridden are still accessible via class objects.
- Look up attributes on instances whenever possible.
class CheckingAccount(Account):
"""A bank account that charges for withdrawals."""
withdraw_fee = 1
interest = 0.01
def withdraw(self, amount):
return Account.withdraw(self, amount + self.withdraw_fee)In the last line, although we have overridden withdraw, we can still access it by class Account.
And we’d better use it instead of copy and paste so that we can keep consistent.
And the third principle, we’d better to write self.withdraw_fee instead of CheckingAccount.withdraw_fee, in case that some instance may have a special withdraw_fee.(Either for further subclasses or giving an instance attribute to particular accounts.)
By the way, we cannot write withdraw_fee only, because the withdraw_fee above appears as an attribute and if we use it directly, it is undefined.
Decomposition and modularization.
Inheritance and Composition
Object - oriented programming shines when we adopt the metaphor.
- Inheritance is best for representing is-a relationships.
E.g., a checking account is a specific type of account.
So,CheckingAccountinherits fromAccount. - Composition is best for representing has-a relationships.
E.g., a bank has a collection of bank accounts it manages.
So, A bank has a list of accounts as an attribute. In this case, accounts do not inherit attributes from bank vice versa.
E.g.
"""Composition"""
class Bank:
def __init__(self):
self.accounts = []
def open_account(self, holder, amount, kind=Account):
account = kind(holder)
account.deposit(amount)
self.accounts.append(account)
return account
def pay_interest(self):
for a in self.accounts:
a.deposit(a.balance * a.interest)
def too_big_to_fail(self):
return len(self.accounts) > 1a little more complicated example:
We should remember that:
- When we create a new instance, we will call
__init__first if it or one of its base class has one. So whenCandBis created,__init__will be call whileA’s instance will not. - When we call
__init__, no matter which class this__init__method is belong to, theself’s class is what our instance is belong to, thus we look up its attribute from this class. So when we callC(1), we call__init__in classB, and assignself.z = self.f(y), thefthere should be found in classCrather than fromBand found inA.