Inheritance Principles of Software System Construction Principles - - PowerPoint PPT Presentation
Inheritance Principles of Software System Construction Principles - - PowerPoint PPT Presentation
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
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 getBalance() : float deposit(amount : float) withdraw(amount : float) : boolean transfer(amount : float, target : Account) : boolean getFee() : float «interface» SavingsAccount getBalance() : float deposit(amount : float) withdraw(amount : float) : boolean transfer(amount : float, target : Account) : boolean getInterestRate() : float
What do you think of this design?
Fall 2011 15-214: Course Introduction 3
getFee() : float getInterestRate() : float
Account Type Hierarchy
«interface» Account getBalance() : float deposit(amount : float) withdraw(amount : float) : boolean transfer(amount : float, target : Account) : boolean monthlyAdjustment() SavingsAccount is a subtype of
- Account. Account
is a supertype of SavingsAccount. CheckingAccount extends Account. All methods from Account are inherited (copied to CheckingAccount)
Fall 2011 15-214: Course Introduction 4
«interface» CheckingAccount getFee() : float «interface» SavingsAccount getInterestRate() : float «interface» InterestCheckingAccount Multiple interface extension If we know we have a CheckingAccount, additional methods are available.
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!
Fall 2011 15-214: Course Introduction 5
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
Implementing Accounts
- All 3 accounts similar
Same code for balance, deposit, withdraw, transfer Differ in monthlyAdjustment Differ in additional features
- Duplication is bad
«interface» Account getBalance() : float deposit(amount : float) withdraw(amount : float) : boolean transfer(amount : float, target : Account) : boolean monthlyAdjustment()
- Duplication is bad
Lots of code to type Fix bugs 3 times Make enhancements 3 times Easily becomes inconsistent What if another new account type is developed?
- Design mantra: once and only
- nce!
Fall 2011 15-214: Course Introduction 6
«interface» CheckingAccount getFee() : float «interface» SavingsAccount getInterestRate() : float «interface» InterestCheckingAccount
Reusing Account Code
public abstract class AbstractAccount implements Account { protected float balance = 0.0; public float getBalance() { return balance; } abstract public void monthlyAdjustment(); // other methods…
«interface» Account getBalance() : float deposit(amount : float) withdraw(amount : float) : boolean transfer(amount : float, target : Account) : boolean monthlyAdjustment() AbstractAccount
protected elements are visible in subclasses an abstract class is missing the implemention of one
- r more methods
} public class CheckingAccountImpl extends AbstractAcount implements CheckingAccount { public void monthlyAdjustment() { balance -= getFee(); } public float getFee() { /* fee calculation */ } }
Fall 2011 15-214: Course Introduction 7
CheckingAccountImpl monthlyAdjustment() getFee() : float AbstractAccount # balance : float + getBalance() : float + deposit(amount : float) + withdraw(amount : float) : boolean + transfer(amount : float, target : Account) : boolean + monthlyAdjustment() «interface» CheckingAccount getFee() : float
an abstract method is left to be implemented in a subclass no need to define getBalance() – the code is inherited from AbstractAccount
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()
Fall 2011 15-214: Course Introduction 9
«interface» CheckingAccount getFee() : float «interface» SavingsAccount getInterestRate() : float «interface» InterestCheckingAccount
Reuse via Wrapping
«interface» Account getBalance() : float deposit(amount : float) withdraw(amount : float) : boolean transfer(amount : float, target : Account) : boolean monthlyAdjustment()
requires a lot of forwarding
Fall 2011 15-214: Course Introduction 10
«interface» CheckingAccount getFee() : float CheckingAccountImpl monthlyAdjustment() { … } getFee() : float { … } getBalance() : float deposit(amount : float) withdraw(amount : float) : boolean transfer(amount : float, target : Account) : boolean
basicAcnt .getBalance()
BasicAccountImpl balance : float getBalance() : float deposit(amount : float) withdraw(amount : float) : boolean transfer(amount : float, target : Account) : boolean
basicAcnt
Reuse via Wrapping, version 2
«interface» Account getBalance() : float deposit(amount : float) withdraw(amount : float) : boolean transfer(amount : float, target : Account) : boolean monthlyAdjustment()
adjustment account
requires two-way dependence
Fall 2011 15-214: Course Introduction 11
«interface» Adjustment doAdjust() SavingsAccountAdjustment doAdjust()
adjustment .doAdjust()
BasicAccountImpl balance : float getBalance() : float deposit(amount : float) withdraw(amount : float) : boolean transfer(amount : float, target : Account) : boolean monthlyAdjustment()
adjustment account float bal = account.getBalance(); float interest = bal * interestRate; account.deposit(interest);
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
- f 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 14 15-214: Course Introduction
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; return success; } /* other methods… */ }
Fall 2011 15-214: Course Introduction 16
invokes a method on this, but starts dispatch in superclass for this call only.
Constructor calls
public class CheckingAccountImpl extends AbstractAcount implements CheckingAccount { private float fee; public CheckingAccountImpl(float initialBalance, float fee) { super(initialBalance);
invokes a particular
super(initialBalance); this.fee = fee; } public CheckingAccountImpl(float initialBalance) { this(initialBalance, 5.00); } /* other methods… */ }
Fall 2011 15-214: Course Introduction 17
invokes another constructor in the same class invokes a particular constructor of the
- superclass. Must be
the first statement of the constructor.
Inheritance Details: final
When modifying a class: cannot extend the class When modifying a method: cannot override the method When modifying a field: cannot assign to the field
- ther than initialization in the constructor
Why might you want to use final in each of the above cases?
Fall 2011 15-214: Course Introduction 18
Inheritance Details: instanceof
- Operator that tests whether an object is of a given class
DataInput input = …; if (input instanceof ObjectInput)
- bj = input.readObject();
- Guidelines for use
OK to find out which case of a datatype you have
Choice among conceptually different abstractions Choice among conceptually different abstractions
- Cf. datatypes in ML
DON’T use to choose among implementations of an abstraction
Implementation choices are supposed to be hidden
- instanceof examples: good or bad?
Test if an object is Serializable Test if a List is an ArrayList Test if a TreeNode is an InternalNode or a LeafNode Test if a InputStream is a FileInputStream
Fall 2011 15-214: Course Introduction 19
Aside: UML class diagram notation
«interface» Account getBalance() : float deposit(amount : float) withdraw(amount : float) : boolean transfer(amount : float, target : Account) : boolean monthlyAdjustment() AbstractAccount
«interface» brand Name of class or interface in top compartment Return type comes after method or field Methods in bottom compartment Dashed line, open triangle arrowhead for implements
Fall 2011 15-214: Course Introduction 20
AbstractAccount # balance : float + getBalance() : float + deposit(amount : float) + withdraw(amount : float) : boolean + transfer(amount : float, target : Account) : boolean + monthlyAdjustment() CheckingAccountImpl monthlyAdjustment() getFee() : float
Solid line, open triangle arrowhead for extends Italics means abstract Italics means abstract Optional visibility: + for public
- for private
# for protected ~ for package (not used much) Fields in middle compartment