Why ADTs Are Not Enough
First problem is early binding of ADT interface to
implementation
Ada: must
say which stack package to use
MyStack is new ArrayStack(
)
can have
only one implementation of an ADT;
two alternate implementations of the same interface are not compatible
package ArrayStack is
push
pop
package ListStack is
push
pop
.
Why early
binding? Want to know function addresses
stack.pop() ...
Solution: dynamic
binding
stack.pop()
selects the right function at runtime depending on the specific implementation
of stack
caller need
not know what implementation is used
Second problem: what about related ADTs?
ADT
GraphicalObject: draw(), moveTo(x,y)
Square:
draw(), moveTo()
Circle:
draw(), moveTo()
Line:
draw(), moveTo()
graphical
editor: draw a list of objects
list
elements can be any graphical object
Ada:
type ObjType is (t_square, t_circle, t_line)
prev, next:
;
case tag is
when t_square =>
s: Square
when t_circle => c: Circle
when t_line =>
l: Line
end case;
end record;
Cumbersome
have to
write lots of code to draw some object
case obj.tag is
when t_square => Square.draw(obj.s);
when t_circle => Circle.draw(obj.s);
when t_line => Line.draw(obj.s);
end case;
have to
invent lots of names
but could
use overloading in Ada
unsafe: uses
union and dynamic type checking
unmaintainable:
to add new kind of object (e.g., Rectangle) --> have to change many case
statements
Solution (again): dynamic binding,
subtyping
automatically
select the right draw function for object depending on its type
Third
problem:
why reimplement every ADT from scratch?
ADTs are often related:
Square and FilledSquare
SortCollection and SortAndMedianCollection
Implementation of FilledSquare
draw = Square.draw(), then fill
moveTo = Square.moveTo
Implementation of moveTo for most graphical objects is the same, but cant
share it
Solution: inheritance
define ADT
as a variant of other ADT specify only the differences:
FilledSquare is like Square,
except
Origin of Inheritance
Observations of the mid-late
1980s:
Productivity
increases can come from reuse
Unfortunately
ADTs are
difficult to reuse - never quite right
all ADTs are
independent and at the same level
Inheritance
solves both - reuse ADTs after minor changes and define classes in a hierarchy
Categories of Languages
That Support OOP
OOP is added
to an existing language
C++ (also
supports procedural and data-oriented programming)
Ada 95 (also
supports procedural and data-oriented programming)
CLOS (also
supports functional programming)
Scheme (also
supports functional programming)
Support OOP,
but have the same appearance and use the basic structure of earlier imperative
languages
Eiffel (not
based directly on any previous language)
Java (based
on C++)
Pure OOP
languages
Smalltalk
Basic Terms of OOP
A Class is an ADT
defining
format of
object (instance variables = fields)
operations
on objects (methods = functions)
operations
for creating new objects
Objects are instances of a class
Inheritance: an ADT inherits all operations of
another ADT, needs to override only those that it wants to change
In the simplest case, a class
inherits all of the entities of its parents
More Basic Terms
A class that inherits is a derived
class or subclass
The class from which another
class inherits is the parent class or superclass
Subprograms that define
operations on objects are called methods
More Basic Terms
The entire collection of methods
of an object is called its message protocol or message interface
Messages have two parts - a
method name and the destination object (often called receiver)
Dynamic binding: call specifies operation name (not
implementation), system selects appropriate implementation (function) at
runtime
Inheritance
can be complicated by access controls to encapsulated entities
A class can
hide entities from its subclasses
A class can
hide entities from its clients
Besides
inheriting methods as is, a class can modify an inherited method
The new one
overrides the inherited one
The method
in the parent is overriden
Class Variables and Methods
There are
two kinds of variables in a class
class
variables - one per class
instance
variables - one per
object
There are two
kinds of methods in a class
class
methods - messages to the
class
instance
methods - messages to
objects
we will
primarily discuss instance variables and methods
class Clock
{
public:
Clock(int init_hour, int
init_min, bool init_am);
void set_time(int hour,
int minute, bool am);
void advance(int
minutes);
int get_hour( ) const;
int get_minute( ) const;
bool is_morning( )
const;
private:
some data
variables----we dont care exactly what
}
class CuckooClock :
public Clock
{
public:
bool is_cuckooing( )
const;
}
Conceptual
view of inheritance: copy all instance variables and methods down to
subclass(es)
or: search
method in class first, recursively search superclass if not found
Clock yourClock; // a local
variable
CuckooClock myClock; //
another variable
// some code
that initializes both objects
yourClock=myClock; // ok
myClock = yourClock; // not ok -- why?
Virtual Functions
A virtual function is a
function that is declared as virtual in the base class and then
redefined by a derived class. To
declare a function virtual, its prototype is preceded by the keyword
virtual in the base class definition.
Example:
class Clock
{
public:
virtual void clock_type(
);
...
}
The redefinition of the function
in the derived class overrides the definition in the base class. Virtual functions behave like any other
member function with the important addition that they support dynamic
binding when accessed via a pointer.
Because an object of the derived
class can always be used when an object of the base class is expected, it
follows that a base class pointer can be used to point to any class derived
from that base. When a base pointer
points to an object that contains a virtual function, C++ determines which
version of the function to call based on the type of the object actually
pointed to by the pointer. This is
called dynamic binding.
Example:
class Clock
{
public:
virtual void clock_type(
) { cout<< ordinary clock;}
...
}
class CuckooClock: public Clock
{
public:
void clock_type( ) {cout
<< cuckoo clock;}
...
}
class MilitaryClock: public Clock
{
public:
void clock_type( ) {cout
<< Military clock;}
...
}
main( )
{
Clock *ptr, ordinary;
CuckooClock fancy;
MilitaryClock plain;
ptr=&ordinary;
ptr->clock_type();
ptr=&fancy;
ptr->clock_type();
ptr=&plain;
ptr->clock_type();
...
}
When non-virtual functions are overridden
the determination of which function to use is made at compile time, this is
called static binding.
Pure Virtual Functions
When a virtual function is not
redefined in a derived class, the version defined in the base class is
used. But in some cases there can be no
meaningful implementation of a function in a base class and you want to ensure
that all derived classes override a virtual function. C++ provides the pure virtual function to handle these
cases.
A pure virtual function is a
function that has no definition within the base class. To declare a pure virtual function, use this
general form:
virtual return_type
function_name(parameter_list)=0;
Example:
class Clock
{
public:
virtual void clock_type(
)=0;
...
}
A derived class must provide its
own definition of a pure virtual function; if it does not, a compile error will
result.
A class that contains at least
one pure virtual function is said to be abstract. You can not create object of an abstract
class. Instead an abstract class
constitutes an incomplete type that is used as a foundation for derived
classes.
Although you cant create objects
of an abstract class you can create pointers and references to an abstract
class.
Polymorphism
polymorphic
(taking many forms)
operating on many objects of many forms (but same interface)
parametric polymorphism: Ada
generics
subtype polymorphism: OO
a
polymorphic variable can be defined in a class that is able to reference
(or point to) objects of the class and objects of any of its descendants
Design Issues for OOPLs
Single and multiple
inheritance
Allocation and
deallocation of objects
Dynamic and static
binding
Multiple levels of
hiding
The exclusivity of
objects
Are subclasses subtypes?
Implementation and
interface inheritance
Type checking and
polymorphism
Single and Multiple Inheritance
Multiple
inheritance inherits from more than one class
Can
create problems
conflicting
definitions (e.g., two or more superclasses define a print method)
repeated
inheritance: same class inherited more than once
Repeated Inheritance
If
A is multiply inherited, should As instance variables be repeated or not?
Answer - it depends on the situation
Eiffel:
can choose field by field
default: one
shared copy
can rename
field to get multiple copies
C++:
can choose per class
default
repeated
use virtual
base class to get a single copy
Repeated Inheritance
Smalltalk, Beta: single inheritance only
Usefulness
of multiple inheritance is not universally accepted
Java:
single inheritance of classes and multiple inheritance of interfaces
Allocation and Deallocation of Objects
From
where are objects allocated?
if they live
in the heap, references to them are uniform
Is
deallocation explicit or implicit?
Implicit: overhead
(about 10%)
Explicit:
errors (40%
of debugging time)
Whose
responsibility
Only C++
Dynamic and Static Binding
Should
all binding of messages to methods be dynamic?
if none are,
you lose the advantages of dynamic binding
if all are, it
is inefficient
C++: default
is static; use virtual functions to get dynamic binding
Java,
Eiffel: dynamic binding (except for final methods in Java)
Multiple levels of hiding
Public
interface for clients
Protected
interface for subclasses
Private
interface for class itself and
Friend (C++)
Package
(Java)
The Exclusivity of Object
Everything
is an object
advantage - elegance and purity
disadvantage - slow operations on simple objects
(e.g., float)
The Exclusivity of Object
Include
an imperative-style typing system for primitives, but make everything else
objects
advantage - fast operations on simple objects and a relatively small typing
system
disadvantage - still some confusion because of the two type systems
Are Subclasses Subtypes?
Does
an is-a relationship hold between a parent class object and an object of
the subclass?
A
variable of the derived type could appear anywhere that a variable of the
parent type is requested
Characteristics of a
subclass that guarantees it is a subtype
can only add variables
and methods or redefine existing methods in a compatible way
If the subclass hides
some of the parents variables and functions or modifies them in an
incompatible way, then it does not create a subtype
What is a compatible
way?
By imposing some
restrictions on the use of inheritance, the language can ensure
substitutability
Type Extension
If
the subclass is allowed to only extend the parent class, then substitutability
is guaranteed
subclass
does not modify any methods and does not hide any of them
Class PARENT
feature
foo (arg: A) is
end; -- PARENT
class CHILD inherit PARENT redefine foo
feature
foo (arg: B) is
end;
-- CHILD
Overriding Methods
Redefined methods must have
the same number of parameters as the method being overridden
The redefined method
must not enforce anymore requirements on the input parameters than the
overridden method
The redefined method may
require stronger requirements on the result than the overridden method
Overloaded methods in Java
Dessert -> Cake ->
ChocolateCake
Scone -> ButteredScone
Moorge(Dessert d, Scone
s) --- definition a
Moorge(Cake c, Dessert
d) --- definition b
Moorge(ChocolateCake cc,
Scone s) --- definition c
Moorge(dessertref,
sconeref) --- uses a
Moorge(chocolatecakeref,
dessertref) uses b
Moorge(chocolatecakeref,
butteredsconeref) uses c
Moorge(cakeref,
sconeref) compile-time error