30
CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

Embed Size (px)

Citation preview

Page 1: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

CSCI 383

Object-Oriented Programming & Design

Lecture 17

Martin van Bommel

Page 2: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

Subtype, Subclass and Substitution

The distinction between subtype and subclass is important because of their relationship to substitution

Recall the argument that asserted a child class has the same behavior as the parent, and thus a variable declared as the parent class should in fact be allowed to hold a value generated from a child class

But does this argument always hold true?

Page 3: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

Subtypes

What is wanted here is something like the following substitution principle:

If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T the behavior of P is unchanged when o1 is substituted for o2

then S is a subtype of T. [Liskov 1988]

Page 4: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

What is a type?

What do we mean when we use the term (data) type in describing a programming language?

A set of values (the type int, for example, describes -2147483648 to 2147483647)

A set of operations (we can do arithmetic on ints, not on booleans)

A set of properties (if we divide 8 by 5 we are not surprised when the result is 1, and not 1.6)

What about when we consider classes (or interfaces) as a system for defining types?

Page 5: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

The Problem of Defining Types

Consider how we might define a Stack ADT

Page 6: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

The Problem of Defining Types

Notice how the interface itself says nothing about the LIFO property, which is the key defining feature of a stack. Is the following a stack?

This class definition satisfies the Stack interface but does not satisfy the properties we expect for a stack, since it violates the LIFO property for all but the most recent item placed into the stack

Page 7: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

The Definition of Subtype

From this example we see that the properties that are key to the meaning of the Stack are not specified by the interface definition.

It is not that we were lazy; Java (like most other languages) gives us no way to specify the properties that an interface should satisfy

Page 8: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

The Definition of Subtype

So now we can better understand the concept of a subtype

A subtype preserves the meaning (purpose, or intent) of the parent

The problem is that meaning is extremely difficult to define. Think about how to define the LIFO characteristics of the stack

Page 9: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

The Substitution Paradox

There is a curious paradox that lies at the heart of most strongly typed object-oriented programming languages

Substitution is permitted, based on subclasses. That is, a variable declared as the parent type is allowed to hold a value derived from a child type

Yet from a semantic point of view, substitution only makes sense if the expression value is a subtype of the target variable

If substitution only makes sense for subtypes and not for all subclasses, why do programming languages based the validity of assignment on subclasses?

Page 10: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

The Undecidability of the Subtype Relationship

It is trivial to determine if one class is a subclass of another

It is extremely difficult to define meaning (think of the Stack ADT), and even if you can it is almost always impossible to determine if one class preserves the meaning of another

One of the classic corollaries of the halting problem is that there is no procedure that can determine, in general, if two programs have equivalent behavior

There is simply no way that a compiler can ensure that a subclass created by a programmer is indeed a subtype

Page 11: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

Is This a Problem?

What does it take to create a subclass that is not a subtype?

The new class must override at least one method from the parent

It must preserve the type signatures But it must violate some important property of the

parent Is this common? Not likely. But it shows you where to look for problem areas

Page 12: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

Subtyping in C++

Subtyping in C++ is provided through inheritance Suppose a class Circle is derived from a class Shape. Then, as mentioned before, a Circle object can be implicitly converted into a Shape object Circle circle;Shape shape = circle;

This is called upcasting in C++ because you’re moving up in the class hierarchy

What happens here is that shape loses all information about circle which isn’t contained within the Shape class (e.g., a radius data member).

This is known as object slicing

Page 13: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

Static and Dynamic Behavior

We are able to derive new types from existing types

We can upcast from a derived type to its base type

This is useful because it allows us to share some code

However, it really does not give us the polymorphism we want to extract from similar types

Page 14: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

Static and Dynamic Behavior

For example, suppose we have the following inheritance hierarchy

class Shape {

...

void draw(void) const;

...

};

class Circle:public Shape{ class Rectangle:public Shape{

... ...

void draw(void) const; void draw(void) const;

... ...

}; };

Page 15: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

Static and Dynamic Behavior

Suppose we declared an array of shapes like thisShape* shapeList[2];shapeList[0] = new Circle(...); // Upcast to Shape*shapeList[1] = new Rectangle(...); // Upcast to Shape*

We might want a function to draw all of our shapes in the shape listvoid draw(int numShapes, Shape* shapeList[]){

for(int i=0; i<numShapes; i++)shapeList[i]->draw();

}

Page 16: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

Static and Dynamic Behavior

Even though we created a Circle object and a Rectangle object, drawShapes only sees them as generic Shape objects

There are different ways of solving this problem, and we’ll look at a couple of them

Page 17: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

Type Fields

One way to solve this polymorphism problem is to manually store the type within every Shape, Circle, or Rectangle object

We could do this by defining an enumerated type, and marking each object in its constructor with its proper type

However, this is very error prone and tends to lead to a lot of switch statements and casting throughout your code

Page 18: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

Static vs. Dynamic Type

So far, we haven’t been able to break the bounds of the declaration type, or the static type, of an object

The declared type of an object or an object pointer has been determining which method will be called

The compiler pays no attention to the true type of the object – which is the type we provided when we allocated it with new.

This is also called the object’s dynamic type

Page 19: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

Static and Dynamic

Much of the power of object-oriented languages derives from the ability of objects to change their behavior dynamically at run time

In Programming languages Static almost always means fixed or bound at compile

time, and cannot thereafter be changed Dynamic almost always means not fixed or bound until

run time, and therefore can change during the course of execution

Page 20: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

Static and Dynamic Typing

In a statically typed programming language, variables have declared types – fixed at compile time(e.g., C++, Java, Pascal)

In a dynamically typed programming language, a variable is just a name. Types are associated with values, not variables. A variable can hold different types during the course of execution (e.g., Smalltalk, Python)

Page 21: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

Arguments For and Against

Static and Dynamically typed languages have existed as long as there have been programming languages. Arguments for and against:

Static typing allows better error detection, more work at compile time and hence faster execution time

Dynamic typing allows greater flexibility, easier to write

(for example, no declaration statements) Both arguments have some validity, and hence

both types of languages will continue to exist in the future

Page 22: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

The Polymorphic Variable

The addition of object-oriented ideas in statically typed languages adds a new twist. Recall the argument for substitution: an instance of a child class should be allowed to be assigned to a variable of the parent class

Page 23: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

Static Class and Dynamic Class

In a statically typed language we say the class of the declaration is the static class for the variable, while the class of the value it currently holds is the dynamic class

Most statically typed OO languages constrain the dynamic class to be a child class of the static class

Animal pet;

Dog fido;

Cat fluffy;

pet = fido; // legal

pet = fluffy; // legal

fido = pet; // not legal!

Page 24: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

Importance of Static Class

In a statically typed object-oriented language, the legality of a message is determined at compile time, based on the static class

A message can produce a compile error, even if no run-time error could possibly arise

class Animal {};

class Dog : Animal {

void bark() { std::cout << "woof"; }

};

Animal *pet = new Dog;

pet->bark(); // generates error, Animals don’t bark

Page 25: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

Binding Times

The binding time of a function call is when the call is bound to a specific function implementation

There generally are two binding times: static binding and dynamic binding

Static binding (also known as early binding) is when the function choice is based on the characteristics of the variable (i.e., the type of the variable)

Static binding generally takes place at compile time Function overloading and overriding in C++ are

examples of static binding

Page 26: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

Binding Times

Dynamic binding (also known as late binding) is when the function choice is based on the dynamic characteristics of the variable (i.e., the type of the data in the variable)

Dynamic binding is deferred until run time In a previous example, if one sends a draw message to

an instance of Rectangle or Circle, one would want the appropriate implementation of draw to be executed

However, if at compile time one knows only that the receiving object is “an instance of a Shape subclass, to be determined at run time”, static binding cannot be used.

In this case, dynamic binding is used Dynamic binding is a powerful but dangerous tool To eliminate the danger of run time errors in C++,

dynamic binding has been limited to virtual functions

Page 27: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

Virtual Functions

The way to retain the behavior of the instantiated type is through the use of virtual functions

To declare a method to be a virtual function, you simply use the virtual keyword when declaring the method in the class definition

When you call a function, it will check the dynamic type of the object before choosing which function to call – this process is reification

Page 28: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

Virtual Functions

For example, we could declare our Shape, Circle, and Rectangle classes as follows

class Shape {

...

virtual void draw() const;

...

};

class Circle : public Shape { class Rectangle : public Shape {

... ...

virtual void draw() const; virtual void draw() const;

... ...

}; };

Page 29: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

Virtual Functions

Now we get the polymorphic behavior we want

Shape* shapeList[maxShapes];

shapeList[0] = new Circle(...);

shapeList[1] = new Rectangle(...);

shapeList[0]->draw(); // Calls Circle::draw()

shapeList[]->draw(); // Calls Rectangle::draw()

Page 30: CSCI 383 Object-Oriented Programming & Design Lecture 17 Martin van Bommel

Virtual Functions

It is important that you declare the function to be virtual throughout your class hierarchy, or its behavior can be quite unexpected

Virtual and non-virtual method can call each other Overloaded operator functions can be virtual

functions A constructor cannot be a virtual function

because it needs to know the exact type to create However, a destructor can be declared as virtual,

and generally should be virtual functions may not seem significant at

first, but they enable a ton of code reuse when using class hierarchies