1 Object-Oriented Programming. 2 = Abstract Data Types package representation of data structure...

Preview:

Citation preview

1

Object-Oriented Programming

2

Object-Oriented Programming = Abstract Data Types

package representation of data structure together with operations on the data structure

encapsulate internal implementation details + Inheritance

support defining new ADT as incremental change to previous ADT(s)

share operations across multiple ADTs + Subclass Polymorphism

allow variables to hold instances of different ADTs + Dynamic Binding

run-time support for selecting right implementation of operation, depending on argument(s)

3

Some OO languages Simula 67: the original Smalltalk-80: popularized OO C++: OO for the hacking masses Java, C#: cleaned up, more portable

variants of C++ CLOS: powerful OO part of Common Lisp Self: very pure OO language Cecil, MultiJava, EML: OO languages

from my research group Emerald, Kaleidoscope: other OO

languages from UW

4

Abstract data types User-defined data structures along with user-

defined operations Support good specification of interface to ADT, hiding

distracting implementation details Prevent undesired dependencies between client and

ADT, allowing implementation to change w/o affecting clients

Allow language to be extended with new types, raising & customizing the level of the language

Called a class in OO languages data structures called objects, or instances of the class operations called methods; data called instance

variables Modules have similar benefits

5

Inheritance Most recognizable aspect of OO languages &

programs Define new class as incremental modification

of existing class new class is subclass of the original class (the

superclass) by default, inherit superclass’s methods & instance

vars can add more methods & instance vars in subclass can override (replace) methods in subclass

but not instance variables, usually

6

Exampleclass Rectangle {

Point center;int height, width;int area() { return height * width; }void draw(OutputDevice out) { ... }void move(Point new_c) { center = new_c; }...

}class ColoredRectangle extends Rectangle {

// center, height, & width inheritedColor color;// area, move, etc. inheritedvoid draw(OutputDevice out) { ... } // override!

}

7

Benefits of inheritance Achieve more code sharing by factoring code

into common superclass superclass can be abstract

no direct instances, just reusable unit of implementation

encourages development of rich libraries of related data structures

May model real world scenarios well use classes to model different things use inheritance for classification of things:

subclass is a special case of superclass

8

Pitfalls of inheritance Inheritance often overused by

novices Code gets fragmented into small

factored pieces Simple extension & overriding may

be too limited e.g. exceptions in real-world

classification hierarchies

9

Subclass polymorphism Allow instance of subclass to be used wherever

instance of superclass expected client code written for superclass also works/is

reusable for all subclasses

void client(Rectangle r) {… r.draw(screen) …

}

ColoredRectangle cr = ...;… client(cr) …// legal, because ColoredRectangle is a subclass of Rectangle

// but what version of draw is invoked?

10

Dynamic binding When invoke operations on object, invoke

appropriate operation for dynamic class of object, not static class/type

ColoredRectangle cr = …;Rectangle r = cr; // OK, because CR subclass of R

r.draw();   // invokes ColoredRectangle::draw!

Also known asmessage passing,virtual function calling,generic function application

11

Method lookup Given a message obj.msg(args) …

Start with run-time class C of obj(the receiver) if msg is defined in C, then invoke it otherwise, recursively search in superclass

of C if never find match, report run-time error

type checker guarantees this won’t happen

12

Dynamic dispatching vs.static overloading Like overloading:

multiple methods with same name, in different classes use class/type of argument to resolve to desired

method Unlike overloading:

resolve using run-time class of argument,not static class/type

consider only receiver argument, in most OO languages C++ & Java: regular static overloading on arguments, too CLOS, Cecil, MultiJava: resolve using all arguments

(multiple dispatching)

13

Example Without dynamic binding, use "typecase" idiom:

forall Shape s in scene.shapes doif s.is_rectangle() then rectangle(s).draw();else if s.is_square() then square(s).draw();else if s.is_circle() then circle(s).draw();else error(“unexpected shape”);end

end With dynamic binding, send message:

forall Shape s in scene.shapes dos.draw();

end What happens if a new Shape subclass is added?

14

Benefits of dynamic binding Allows subclass polymorphism and dynamic

dispatching to class-specific methods Allows new subclasses to be added without

modifying clients Allows more factoring of common code into

superclass, since superclass code can be “parameterized” by “sends to self” that invoke subclass-specific operations

"Template method" design pattern

15

Pitfalls of dynamic binding Tracing flow of control of code is

harder control can pop up and down the class

hierarchy

Adds run-time overhead space for run-time class info time to do method lookup

but only an array lookup (or equivalent),not a search

16

Issues inobject-oriented language design Object model:

hybrid vs. pure OO languages class-based vs. classless (prototype-based)

languages single inheritance vs. multiple inheritance

Dispatching model: single dispatching vs. multiple dispatching

Static type checking: types vs. classes subtyping subtype-bounded polymorphism

17

Self

18

Self A purely object-oriented language,

developed as a Smalltalk successor in 1986/7 Every thing is an object

including primitives like numbers, booleans, etc. no classes, only prototypes first-class functions (aka blocks) are objects even methods are objects

Every action is a message operations on primitives control structures access & assignment to instance variables

Scoping is inheritance Theme: simplicity (uniformity) yields power

19

Self objects An object is just a list of slots

A slot is a key/value pair The contents of a slot is (a reference to)

another object Example: (| x = 3. y = 4. |)

x

y

3

4

20

Accessing slots The only thing you can do with an

object is send it a message To fetch the contents of an object's

slot, send the slot's name as a message to the object

Example:let aPoint = ( | x = 3. y = 4. | )

aPoint x "send x to aPoint, yielding 3"

21

Methods A method is just a special kind of object

stored in a slot Special because it has code that runs when it's

looked up in a slot Example:

let aPoint =

( | x = 3.

y = 4.

distanceToOrigin = ((self x squared + self y squared) sqrt

)

| )

aPoint distanceToOrigin "yields 5"

22

Syntax of messages Unary messages: a simple identifier written after the

receiver right-associative

aPoint distanceToOrigin self x self x squared (…) sqrt

Binary messages: punctuation symbol(s) written between its two arguments

any sequence of punctuation symbols allowed; user-defined operators

lower precedence than unary messages all binary messages have same precedence and are left-

associative x squared + y squared 3 + 4 * 5 yields 35

Keyword messages: later…

23

Sends to self In a method, the name of the receiver of

the message is self If no receiver specified, then it's

implicitly self E.g.

self x squared can be written x squared distanceToOrigin = (

(x squared + y squared) sqrt )

Makes method calls as concise as (traditional) instance variable access

24

Making new objects Can make new objects by:

writing them down explicitly (as we've done), or cloning an existing object (the prototype)

a shallow copy of the object

Example:let otherPoint = aPoint clone.

x

y

3

4d2o

(…) sqrt

x

y

d2o

25

Mutable slots Slots initialized with = are immutable Slots initialized with <- are mutable To change a slot named x, send the object the x: message with the new value returns the receiver, e.g. for additional updates

Example:let aPoint = (| x <- 3. y <- 4. |).

aPoint x: 5. "updates aPoint's x slot to refer to 5"aPoint x "yields 5"aPoint y: aPoint y + 1. "increments y"(aPoint x: 0) y: 0. "sets aPoint to be the origin"

26

Assignment slots When a mutable slot named x is declared, two

slots are created in the object: one named x referring to the slot's (current) contents one named x: referring to the assignment primitive

Example: (| x <- 3. y <- 4. |)

x: <-

y

3

4

x

y: <-

27

Keyword messages A keyword message is an identifier

followed by a colon It takes an argument after the colon aPoint x: 5

The message is x: The receiver is (the result of evaluating) aPoint The argument is (the result of evaluating) 5

Also have keyword messages that can take more than one argument (later…)

28

Methods with arguments A method object can take one or more

arguments by declaring slots whose names begin with colons One argument slot for each argument that can be

accepted according to the slot name 1 for binary messages 1 or more for keyword messages

Example:(| …

+ = (| :p | (clone x: x + p x) y: y + p y )

|)

Shorthand: put argument name in slot name + p = ( (clone x: x + p x) y: y + p y )

29

A scenario… We define the first point as

let aPoint =

( | x = 3.

y = 4.

distanceToOrigin = ( … )

+ p = ( … )

… "lots of other methods on points" | )

Then we make lots of other points via cloning… aPoint clone … p3 + p9 …

Then we want to add a new method to all points how?

30

Inheritance Introduce sharing through inheritance Put shared slots (e.g. methods) into one

object (called a traits object) Put object-specific slots (e.g. instance vars)

into another object (called a prototype object)

Have the prototype inherit the traits By adding a slot marked as a parent slot using an

asterisk Clone the prototype to make new objects

They'll also inherit the same traits object

31

Examplelet pointTraits =

( | distanceToOrigin = ( … )

+ p = ( … )

… "lots of other methods on points" | )

let pointProto =( | x <- 0. "default initial coordinates" y <- 0.

parent* = pointTraits. "inherit shared code" | )

let p1 = (pointProto clone x: 3) y: 4.

32

The result

(…) sqrtd2o

+

… clone …

x: <-

y

x

y: <-

parent

x: <-

y

x

y: <-

parent

3

4

0

0

pointTraits

pointProto p1

33

Message lookup, revisited If a message msg is sent to an object:

If the object contains a slot named msg, get the object referenced by the slot

If it's the assignment primitive, do an assignment to the corresponding data slot

If it's a method, run its body and return the result (more later)

Otherwise, just return the contents Otherwise, look for a slot marked as a parent

slot If found, then recursively look up the message in the

object referred to by the parent slot(Parents can contain their own parent slots, etc.)

Otherwise, report a "message not understood" error

34

Invoking a method To run a method object:

Clone the method object to make a method activation object

a stack frame! Initialize the argument slots of the

cloned method to the argument objects Evaluate the expression(s) in the body Return the result of the last expression

But what about self?

35

Self self is an implicit argument slot of

every method What is the self argument?

The original object that received the message?

Or the object containing the method? Or something else?

Consider a message p1 + p1; in the + method inherited from pointTraits, what's self bound to?

36

Local slots Methods can have slots

E.g. argument slots Plus regular slots which act like local variables

Sends to implicit self actually start the message lookup in the currently executing method activation object The self slot of the method is a parent slot, so

that lookup continues to search the receiver if a message to implicit self doesn't match a local slot

The method activation is a refinement of the receiver Example:

+ = (| ":self*" :p | … x + p x … )

37

Multiple arguments To send a message with multiple arguments,

use extended keyword messages Interleave keyword message part with argument

expressions In Self, the first keyword message part must start

with a lower-case letter; the rest must start with an upper-case letter

Example: pointTraits newX: x Y: y

message/slot name: newX:Y: receiver: pointTraits arguments: x and y

pointTraits = (| newX: x Y: y = ( … ). … |)

38

Summary, so far Saw syntax & semantics of declaring

objects, sending messages, inheritance, assignment

Didn't see classes… Didn't see constructors… Didn't see static methods & variables vs.

instance methods & variables… Didn't see different syntax for accessing

instance variables vs. calling methods…

39

What do classes usually offer? Can define the methods and instance variables of its

instances Self lets each object be self-sufficient & self-describing Self programmers use shared traits objects as a way to share

things across all instances of a class (Doesn't work as well for instance variables)

Can inherit from other classes Self allows individual objects to inherit directly from other objects

Self inheritance is used for both class inheritance and class instantiation

Can have static/class methods and instance variables Self programmers can define separate objects (e.g. factories) if

they want these things Can define constructors

Self programmers define regular methods which use clone to do this

40

Benefits of prototype model Self is much simpler by not having separate

class and instance concepts Also:

makes singleton objects natural avoids the problem of "what's the class of a

class? and what's its class? and …" no metaclasses

allows instances to inherit run-time state from other instances

allows inheritance to be changed at run-time, by making parent slots assignable

called dynamic inheritance

41

Benefits of uniform messaging Traditionally, instance variables and

methods are accessed differently Self accesses them both via messages

Easy to change implementation from data to code or vice versa, without affecting clients

Easy to override data, and override code with data

Still syntactically concise C#'s attributes are a clumsy version of

this

42

Benefits of uniform objects Primitive values are first-class objects

Inherit from predefined traits objects, etc., for their behavior

Send them messages just like other objects To make this work using expected syntax,

syntax of "operations" are available to all objects

Can add user-defined methods on them, just like other objects

43

First-class functions Self includes first-class functions as objects

Called "blocks" Written like a method, except use [ … ]

instead of ( … ) Invoke a block by sending it the value

message (or value: if it takes an argument, or value:With:With: if it takes 3 arguments) [| :arg1. :arg2 | code ] means(| value: arg1 With: arg2 = ( code ) |)

44

Lexical scoping Blocks can be nested in methods

Can access slots of lexically enclosing method

Implemented by giving block activation objects an implicit anonymous parent slot that inherits from the lexically enclosing method activation object

Lexical scoping is just inheritance!

45

Control structures using blocks Self has no built-in control structures Instead, use objects, messages, and

blocks to program them all, entirely in (extensible) user code

Example:let true =(| parent* = boolTraits. ifTrue: trueBlock False: falseBlock = ( trueBlock value ) |)

let false = (| … falseBlock value … |)

(x < 0) ifTrue: ['neg'] False: ['non-neg']

46

Iterators To preserve abstraction of

collections, each defines one (or more) iterator methods

Most basic: do: aList do: [ |:elem| elem print. ].

Others: keysAndValuesDo:, includes:, includesKey:, filterBy:, map:, …

47

Example: association listlet assocListTraits = (|

parent* = orderedCollectionTraits. "lots of cool methods"assoc = (| key. value. next. |). "implicit <-nil"assocsDo: aBlock = (

| assoc <- head |[assoc != nil] whileDo: [

aBlock value: assoc. assoc: assoc next. ] ).

keysAndValuesDo: aBlock = (assocsDo: [|:assoc| aBlock value: assoc key With: assoc value] ).

|).at: k Put: v = ( "should check for existing assoc, too"

assocsDo: [|:assoc| (assoc key = k) ifTrue: [ assoc value: v. ^self ] ]. "^ does early

return"head: ((assoc clone key: k) value: v) next: head. ).

let assocListProto = (| parent* = assocListTraits. head. |).

48

A clientlet phoneBook = assocListProto clone.

phoneBook at: 'Sylvia' Put: '123-4567'.

phoneBook at: 'Andrei' Put: 'unlisted'.

phoneBook keysAndValuesDo: [|:name. :number|

('calling ' + name + '...').print.

number makeCrankCall.

].

49

Top-level environment There's a distinguished object that's

the top-level environment It defines or inherits slots for all

"global" names, e.g. pointTraits, assocListProto, …

A Self read-eval-print interpreter executes expressions in the context of this object It's the implicit self of the read-eval-print

loop

50

Updating existing objects Introduce (true) primitives to modify

existing objects obj _AddSlots: (| slots |)

adds slots to obj, replacing any that already exist

obj _DefineSlots: (| slots |) like _AddSlots:, plus removes all others from obj

No need for special let construct let pointTraits = (|…|) is really_AddSlots: (| pointTraits = (|…|) |)

51

Cecil

52

Cecil Inspired by Self:

A classless object model Uniform use of messages for everything

Inspired by CLOS: Multiple dispatching

Extends both OO and functional programming styles

Inspired by Trellis: Static typechecking Optional

Support mixing dynamically and statically typed code

53

Bindings Use let to define (local and global)

variables add var keyword to allow assignment,

otherwise immutable must initialize at declaration

let inc := 1;

let var count := 0;

count := count + inc;

54

Functions Use method to define functions

last expression evaluated is returned can overload name for different numbers of

argumentslet var count := 0;

method foo(a, b, c) {count := count + 1;let var d := a + b;let e := frob(d, c);d := d + e;d + 5 }

method frob(x, y) { x - frob(y) + 1 }

method frob(x) { - x / 5 }

55

Closures: first-class functions Code bracketed in braces is a 0-argument

function valuelet closure := { factorial(10) + 5 };

Evaluation of closure body delayed until invoked by eval:

eval(closure)   3628805 To allow arguments to closure, add &(x,y,z)

prefix;invoke passing extra arguments to eval:

let closure2 := &(n){ factorial(n) + 5 };

...eval(closure2, 10)   3628805

Like ML's fn, Self's blocks: anonymous, lexically scoped, first-class

56

Glitch: returning closures In current Cecil implementation,

by default,closures cannot safely be returned out of theirlexically enclosing scope a glitch in the Vortex

implementation, not the Cecil language

can crash Vortex mysteriously prevents currying, compose,

closures in data structures, ...

57

Avoiding the glitch To be able to return closures, use && rather than &:

method add_x(x) { &&(y){ x + y } }

let add_2 := add_x(2);

let add_5 := add_x(5);

eval(add_2, 4)    6eval(add_5, 4)    9

58

Using closures in control structures As in Self, all traditional (and many

non-traditional) control structures are implemented as regular Cecil functions, with closures passed by callers supporting the necessary evaluation-only-on-demand

For simple lazy or repeated evaluation:if(test, { then_value }, { else_value })

test1 & { test2 }while({ test }, { body })

59

More examples For iteration with arguments:

for(start, stop, &(i){ body })

do(array, &(elem){ body })

do_associations(table,

&(key,value){ body })

For exception handling:fetch(table, key, { if_absent })

For 3-way branching:compare(i, j, {if_lt}, {if_eq}, {if_gt})

60

An example-- this is a factorial method

method factorial(n) {

if(n = 0,

   { 1 },

   { n * factorial(n - 1) }) }

-- call factorial here:

factorial(7)

61

Non-local returns Support exiting a method early

with a non-local return from a nested closure like ^ in Self like a return statement in C

{ ...; ^ result }

{ ...; ^ } -- return void

62

Examplemethod fetch(table, key, if_absent) {do_associations(table, &(k, v){ if(k = key, { ^ v });});eval(if_absent) }

method fetch(table, key) {fetch(table, key, { error("key " || print_string(key) ||    " not found") }) }

fetch(zips, "Seattle", { 98195 })

63

Objects To define a new kind of ADT, use an object declaration

object Point;

No classes! To make a new "instance" of that

ADT, use an object isa … expression

method new_point() {object isa Point }

No special constructors!

64

Methods of objects To define a method "in" an object,

write the method outside the object but specialize the method to the object by adding @obj after the first argument (which acts like the receiver argument)

method area(p@Point) {p.x * p.y }

method shift(p@Point, dx, dy) {p.x := p.x + dx;p.y := p.y + dy; }

65

Fields of objects To declare an instance variable, use a field

declaration specialize the field to the object "containing" the field add var keyword to allow assignment, otherwise

immutable fields can be given default initial values at declaration fields can be given initial values at object creation

supports immutable, initialized fields!

var field x(p@Point) := 0;var field y(p@Point) := 0;method new_point(x0, y0) {object isa Point { x := x0, y := y0 } }

66

Fields accessed by messages Field declarations implicitly produce 1 or 2

accessor methods: get accessor: given object, return field contents set accessor (for var fields): given object & field’s new

contents, modify field Manipulate field contents solely by invoking

these methodsvar field x(p@Point) := 0;

method x(p@Point) { ... fetch p.x’s contents,

initially 0 ...}method set_x(p@Point,

new_value) { ... update p.x to be

new_value ... }

-- increment p.x:set_x(p, x(p) + 1)

67

Syntactic sugar For syntactic convenience, any call

can be written using dot notation:p.x              x(p)

p.x := p.x + 1   set_x(p,x(p)+1)

p.shift(3,4)     shift(p, 3, 4)

Infix & prefix operators (e.g. +) are really messages, too

68

Inheritance Make new ADTs from old ones via isa inheritance link

object ColoredPoint isa Point;

child/parent, a.k.a. subclass/superclass inherit all method & field declarations can add new methods & fields,

specialized on child object can override methods & fields

69

Exampleobject ColoredPoint isa Point;

-- inherit all Point fields and methods-- add some new ones:field color(cp@ColoredPoint);

method new_colored_point(x0, y0, c0) {object isa ColoredPoint { x := x0, y := y0, color := c0 } }

let p := new_colored_point(3,4,"Blue");

print(p.color);   "Blue"p.shift(2,-2); -- invoke inherited

methodprint(p.x);       5

70

Overriding of methods Child can override inherited method by

defining its ownobject Point;method draw(p@Point) { … }

object ColoredPoint isa Point;method draw(p@ColoredPoint) { … }

let p := new_point(3,4);p.draw;   -- invoke's Point’s draw

let cp := new_colored_point(5,6,"Red");

cp.draw;  -- invokes ColoredPoint's draw

71

Overloaded methods and dynamic dispatching Can overload methods two ways:

same name but different numbers of arguments

same name & number of arguments,but different specializer objects

Specializer-based overloading resolved by using run-time class of receiver argument(a.k.a. dynamic dispatching, message sending)

72

Multimethods Any argument, not just the receiver, can be

specialized to an object

method =(p1@Point, p2@Point) {p1.x = p2.x & { p1.y = p2.y } }

method =(cp1@ColoredPoint, cp2@ColoredPoint){

cp1.x = cp2.x & { cp1.y = cp2.y } &

{ cp1.color = cp2.color } }

A message invokes theunique most-specific applicable method

73

Method lookup rules Find all methods with the right name and number of

arguments that apply A method applies if the actual run-time objects are equal

to or inherit from all the method's specializers, where present

Report "message not understood" if no applicable methods Pick the applicable method whose specializers are

uniformly most specific A specializer is more specific than another if it inherits

from the other A method overrides another if all of its specializers are at

least as specific as the other's Report "message ambiguous" if no single best method

Recommended