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 can’t 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, i.e., 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 don’t 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 can’t 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 A’s 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 parent’s 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

•      In Java, redefined methods must have the same name and signature as the method being overridden so substitutability is ensured