Inheritance Principles of Software System Construction Principles of Software System Construction Jonathan Aldrich Bill Scherlis Spring 2012
Bank Accounts � What kind of accounts does a bank offer? What are their basic features? Fall 2011 15-214: Course Introduction 2
Account Interfaces «interface» CheckingAccount «interface» SavingsAccount getBalance() : float getBalance() : float deposit(amount : float ) deposit(amount : float ) withdraw(amount : float ) : boolean withdraw(amount : float ) : boolean transfer(amount : float , transfer(amount : float , target : Account) : boolean target : Account) : boolean getFee() : float getFee() : float getInterestRate() : float getInterestRate() : float � What do you think of this design? Fall 2011 15-214: Course Introduction 3
Account Type Hierarchy «interface» Account getBalance() : float CheckingAccount SavingsAccount is deposit(amount : float ) extends Account. a subtype of withdraw(amount : float ) : boolean All methods from Account. Account transfer(amount : float , Account are target : Account) : boolean is a supertype of inherited (copied to SavingsAccount. monthlyAdjustment() CheckingAccount) «interface» CheckingAccount «interface» SavingsAccount getFee() : float getInterestRate() : float If we know we have a CheckingAccount, «interface» InterestCheckingAccount Multiple interface additional methods extension are available. Fall 2011 15-214: Course Introduction 4
The Power of Object-Oriented Interfaces � Polymorphism � Different kinds of objects can be treated uniformly by clients � Keep a list of all accounts � Use accessor (getBalance) and mutator (deposit, transfer) methods � Each object behaves according to its type � monthlyAdjustment(): fee for checking, interest for savings void adjustAll() { void adjustAll() { for (Account acct : getAllAccounts()) acct.monthlyAdjustment() } � Impact � Add new kind of account: client code does not change � Important because this kind of change happens often! Polymorphism is the key strength of OO � Difficult to encode in other paradigms and languages (C, ML, etc.) � Will come back repeatedly in this class Fall 2011 15-214: Course Introduction 5
Implementing Accounts � All 3 accounts similar «interface» Account � Same code for balance, getBalance() : float deposit, withdraw, transfer deposit(amount : float ) � Differ in monthlyAdjustment withdraw(amount : float ) : boolean transfer(amount : float , � Differ in additional features target : Account) : boolean monthlyAdjustment() � � Duplication is bad Duplication is bad � Lots of code to type «interface» CheckingAccount «interface» SavingsAccount � Fix bugs 3 times getFee() : float getInterestRate() : float � Make enhancements 3 times � Easily becomes inconsistent � What if another new account «interface» InterestCheckingAccount type is developed? � Design mantra: once and only once! Fall 2011 15-214: Course Introduction 6
Reusing Account Code an abstract class is public abstract class AbstractAccount «interface» Account missing the implements Account { implemention of one getBalance() : float deposit(amount : float ) or more methods protected float balance = 0.0; withdraw(amount : float ) : boolean public float getBalance() { transfer(amount : float , target : Account) : boolean protected elements return balance; monthlyAdjustment() are visible in } subclasses abstract public void monthlyAdjustment(); // other methods… AbstractAccount AbstractAccount } an abstract method is left «interface» CheckingAccount # balance : float to be implemented in a + getBalance() : float getFee() : float public class CheckingAccountImpl + deposit(amount : float ) subclass + withdraw(amount : float ) : boolean extends AbstractAcount + transfer(amount : float , implements CheckingAccount { target : Account) : boolean + monthlyAdjustment() public void monthlyAdjustment() { balance -= getFee(); } public float getFee() { /* fee calculation */ } CheckingAccountImpl no need to define } monthlyAdjustment() getBalance() – the code is getFee() : float inherited from AbstractAccount Fall 2011 15-214: Course Introduction 7
Inheritance vs. Subtyping � Inheritance � A class reuses code from a superclass � class A extends B � Inheritance is for code reuse � Write code once and only once � Code from superclass implicitly available in subclass � Subtyping � A class implements a (Java) interface � class A implements I � A class implements the (implicit) interface of another class � class A extends B : both subtyping and inheritance � Subtyping is for polymorphism � Accessing objects the same way , but getting different behavior � Subtype is substitutable for supertype Fall 2011 15-214: Course Introduction 8
Challenge: Is Inheritance Necessary? � Can we get the same amount of code reuse using only interfaces? «interface» Account getBalance() : float deposit(amount : float ) withdraw(amount : float ) : boolean transfer(amount : float , target : Account) : boolean monthlyAdjustment() «interface» CheckingAccount «interface» SavingsAccount getFee() : float getInterestRate() : float «interface» InterestCheckingAccount Fall 2011 15-214: Course Introduction 9
Reuse via Wrapping «interface» Account getBalance() : float deposit(amount : float ) withdraw(amount : float ) : boolean transfer(amount : float , requires a lot of target : Account) : boolean monthlyAdjustment() forwarding «interface» CheckingAccount getFee() : float basicAcnt CheckingAccountImpl BasicAccountImpl monthlyAdjustment() { … } balance : float getFee() : float { … } getBalance() : float getBalance() : float basicAcnt deposit(amount : float ) deposit(amount : float ) withdraw(amount : float ) : boolean .getBalance() withdraw(amount : float ) : boolean transfer(amount : float , transfer(amount : float , target : Account) : boolean target : Account) : boolean Fall 2011 15-214: Course Introduction 10
Reuse via Wrapping, version 2 «interface» Account getBalance() : float requires two-way deposit(amount : float ) dependence withdraw(amount : float ) : boolean transfer(amount : float , target : Account) : boolean monthlyAdjustment() adjustment adjustment account account BasicAccountImpl «interface» Adjustment balance : float doAdjust() getBalance() : float deposit(amount : float ) withdraw(amount : float ) : boolean transfer(amount : float , SavingsAccountAdjustment target : Account) : boolean doAdjust() monthlyAdjustment() float bal = account.getBalance(); float interest = bal * interestRate; adjustment account.deposit(interest); .doAdjust() Fall 2011 15-214: Course Introduction 11
Inheritance vs. Delegation � The wrapping strategy described above is called delegation � Delegation can be cleaner than inheritance � Reused code in a separate object � Interfaces between objects � However, inheritance eliminates boilerplate � Forwarding functions � Forwarding functions � Recursive dependencies Fall 2011 15-214: Course Introduction 12
Overloading � When an object has multiple methods with the same name but different argument types � Who can name an example? Why would anyone do this? Fall 2011 15-214: Course Introduction 13
Method dispatch, revisited Example call: x.foo(5); � Step 1 (compile time): determine what class to look in � Look at the static type of the receiver (x in the example above) � Step 2 (compile time): determine the method signature � Find all methods in the class with the right name � Includes inherited methods � Includes inherited methods � Keep only methods that are accessible � E.g. a private method is not accessible to calls from outside the class � Keep only methods that are applicable � The types of the actual arguments (e.g. 5 has type int above) must be subtypes of the corresponding formal parameter type � Select the most specific method � m1 is more specific than m2 if each argument of m1 is a subtype of the corresponding argument of m2 � Keep track of the method’s signature (argument types) for run-time Fall 2011 15-214: Course Introduction 14
Method dispatch, revisited � Step 3 (run time): Determine the run-time class of the receiver � Look at the object in the heap to find out what its run-time class is � Step 4 (run time): Locate the method to invoke � Starting at the run-time class, look for a method with the right name and argument types that are identical to those in the method found and argument types that are identical to those in the method found statically (step 2) � If it is found in the run-time class, invoke it. � Otherwise, continue the search in the superclass of the run-time class � This procedure will always find a method to invoke, due to the checks done during static typechecking Fall 2011 15-214: Course Introduction 15
Extending methods with super public abstract class AbstractAccount implements Account { protected float balance = 0.0; public boolean withdraw( float amount) { if (amount > balance) return false ; balance -= amount; return true ; } /* other methods… */ } /* other methods… */ } public class ExpensiveCheckingAccountImpl extends AbstractAcount implements CheckingAccount { public boolean withdraw( float amount) { boolean success = super .withdraw(amount) if (success) balance -= ATM_FEE; invokes a method on return success ; this , but starts dispatch in superclass } for this call only. /* other methods… */ } Fall 2011 15-214: Course Introduction 16
More recommend