CS 541 Lecture -*- Outline -*- * Inheritance mechanism for incremental programming allows one to extend function of class without changing code ** Hierarchy of classes Stack is subclass of Object, superclass of Stack is Object (see figure 2 of TR91-22a) inherits methods + instance variables ** Example: Adding size to stacks ------------------------------------------ SIZED STACK > Stack addSubClass: #SizedStack \ instanceVariableNames: 'size' "methods for SizedStack" new ------------------------------------------ ...new super new. size <- 0 push: anElem size <- size + 1. super push: anElem pop size <- size - 1. super pop size ^size explanation follows *** Inheriting data Show the layout of storage in instances of SizedStack the Stack is *not* contained in the SizedStack (that would be a different design) *** Inheriting operations Explain meaning of self and super -infinite loops from calling with self ------------------------------------------ STEPS IN MESSAGE SENDING let receiver = object that is sent message let static-class = class where code being executed is defined let rc = if receiver is not super, then receiver's (dynamic) class else if receiver is "super" then superclass of static-class 1. [fetch] look for method in class rc (a) if found: (b) if not found and rc is not Object: (c) if not found and rc is Object: 2. [execute] bind self to bind super to bind formals to allocate temporary vars (|temp|), execute the code of the method ------------------------------------------ ... 1(a) go to step 2 ... 1(b) set rc to superclass of rc, goto 1 ... 1(c) error (message not understood) ... receiver, receiver, actuals **** semantic examples without super Consider the following session with the Little Smalltalk interpreter: ------------------------------------------ MESSAGE PASSING EXAMPLE > Object addSubClass: #C \ instanceVariableNames: '' > C addSubClass: #D \ instanceVariableNames: '' "methods for class C" m1 ^self m2 m2 ^ #C "methods for class D" m2 ^ #D ------------------------------------------ Q: what is the result of x <- D new. x m1 ? returns the symbol "D". Discuss how, draw picture Q: What if we added another method to D... m3 ^ super m2 and then ran x m3? Q: What if m2's body was "^ self m2" instead? **** semantic examples with super ------------------------------------------ MESSAGE PASSING EXAMPLE WITH SUPER > Object addSubClass: #C \ instanceVariableNames: '' > C addSubClass: #D \ instanceVariableNames: '' "methods for class C" m1 ^ self m2 m2 ^ 'C' m3 ^ 'm3' "methods for class D, subclass of C" m2 ^ super m3 m3 ^ 'E' ------------------------------------------ Q: What is the result (if any) of the expression "C new m1"? It's the string 'C'. Q: What is the result (if any) of the expression "(D new) m1"? It's the string 'm3'. If you got 'E' you don't see how super works. ** Example: CachedStack ------------------------------------------ CACHED STACK > Stack addSubClass: #CachedStack \ instanceVariableNames: 'cache' "methods for CachedStack" ------------------------------------------ "new is inherited" push: anElem (cache notNil) ifTrue: [super push: cache]. cache <- anElem pop (cache isNil) ifTrue: [super pop] ifFalse: [cache <- nil]. top (cache notNil) ifTrue: [^cache] ifFalse: [^super top] isEmpty ^(cache isNil) and: [super isEmpty] "example of use" c <- CachedStack new. c push: 1. c push: 2 *** How to define printString? Smalltalk's read-eval-print uses printString inherit it? following works in ST-80, but accesses elems instance variable (coupling between super and subclass) illegal in LST (?) ------------------------------------------ INHERITING PRINT METHODS printString for CachedStack "following is bad style, but works" printString | s | s <- 'Stack ('. (cache notNil) ifTrue: [s <- s , ' ' , cache printString]. elems do: [ :e | s <- s , ' ' , e printString]. ^ s , ' )' problems with the above: - ------------------------------------------ ... access to instance variables of super is dangereous ... repeats code, so would like to inherit it *** Inheriting printString by factoring out common parts ------------------------------------------ INHERITANCE BY FACTORING OUT COMMON PARTS "methods for Stack" printString ^ 'Stack(' , self printStringElements , ')' printStringElements | s | s <- ''. elems do: [ :e | s <- s , ' ' , e printString]. ^s FOR YOU TO DO Define whatever methods are *needed* to have printString work for CachedStack ------------------------------------------ "instance method for CachedStack" printStringElements | s | s <- ''. (cache notNil) ifTrue: [s <- s , ' ' , cache printString]. s <- s , super printStringElements. "*could have referenced elems here in ST-80" ^s conclusion: subclassing allows code sharing abstraction ** exercise Q: Can you write Gague, like Counter, but also has a decrement method? ** Design issue: Single vs. multiple inheritance ------------------------------------------ SINGLE VS. MULTIPLE INHERITANCE def: a language has *single inheritance* if each class has def: a language has *multiple inheritance* if each class may have Motivating problem: what if want SizedCachedStack? ------------------------------------------ ... exactly one superclass examples: Smalltalk, Java note, inheritance is thus hierarchical (tree like) (In Smalltalk-80, class hiererchy is exactly parallel Stack class inherits from Object class = Class) ... more than one superclass examples: CLOS, C++, ... (often such languages also permit a class to have no superclasses) with single inheritance SizedCachedStack has to be subclass of either Sized or Cached problems: what if two (or more) define same instance variable or same method? efficiency (have to hash to get instance vars)