170 likes | 183 Views
Explore the distinction between subtypes and subclasses, and the importance of their relationship to substitution. Learn about the difficulties in defining types and the paradox of substitution. Discover the undecidability of the subtype relationship and its implications in programming languages. Gain insights into subtyping in C++ and the concept of object slicing.
E N D
CSCI-383 Object-Oriented Programming & Design Lecture 16
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?
Subtypes • What is wanted here is something like the following substitution principle: If for each object o1of type S there is an object o2of 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]
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?
The Problem of Defining Types • Consider how we might define a Stack ADT
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
Syntax vs. Semantics • Although not an exact correspondence, the issue of subclass vs. subtype is in some way similar to the more familiar language distinction between syntax and semantics • Syntax deals with how a statement can be written, just as subclasses deal with how a class is declared • Semantics deals with what a statement means, just as subtypes deal with how a child class preserves the meaning of a parent class
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. And 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
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
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?
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
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
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
Friends (not the TV show) • A friend function is a function that explicitly is given full access to the protected and private members of a class, even though it is not a member of that class (or of any of its derived classes) • Friend functions violate the rules of encapsulation, so they should be used sparingly • Two situations in which one would use friend functions are • A need exists to provide a function with access to members of more than one class • There is a need to pass all arguments through the argument list
Friends (not the TV show) • Friend functions can be either methods of another class or global functions • Handout #4 contains a simple example in which the friend function would be justified • If many methods of one class need direct access to the protected and private members of another class, the first class can be designated as a friend class to the second • For example, in handout #4 one could change the declaration friend 3DPoint::transform(const Matrix&) const; to friend class 3DPoint;
Friends (not the TV show) • A friend function (or class) may be declared among the public, protected, or private members of the class to which it is a friend • One should have strong justification for declaring friend functions • One should have very strong justification for declaring friend classes • Note: Since overloadings of the I/O operators must be implemented as nonmember (global) functions, they are usually made friends of the class friend ostream& operator<<(ostream& out, const Matrix& m)