Object-Oriented Programming


I. Inheritance: The sharing of an interface among a group of related
     classes. One class is the superclass and declares the interface.
     The remaining classes are subclasses of this superclass and
     inherit the superclass's interface.

    A. Examples

        1. Graphical Objects: A graphical library might provide a
	   programmer with a number of objects, including rectangles,
	   lines, bitmaps, and text objects. These objects share
	   a number of common methods, such as:

	   a. draw: draws the object on the screen.
	   b. pointInObj: takes a point and returns true if the point
	       is in the object and false otherwise. Useful for 
	       determining if the mouse cursor is over an object.
	   c. getBounds: returns the smallest rectangle that encloses
	       the object. Useful for deleting objects since the easist
	       way to erase an object is to draw an opaque rectangle
	       that is just large enough to cover the object. 

	   One might declare a superclass object called GraphicalObject
	   and define its interface to include draw, pointInObj, and
	   getBounds. One would then declare Rectangle, Line, Text, and
	   Bitmap to be subclasses of GraphicalObject. Each of these
	   subclasses would inherit GraphicalObject's interface (i.e.,
	   they share it)

	2. Data Structures: A data structure can often be implemented
	   in a number of different ways but each implementation should
	   share the same interface so the implementations can be swapped
	   without rewriting the application. This goal can be
	   accomplished by declaring a superclass for the data structure
	   that defines its interface and then declaring subclasses
	   that inherit the interface and which provide different
	   implementations. For example, one might declare a Stack class
	   that has the methods pop, push, isEmpty, and top. One would
	   then declare StackArray and StackList to be subclasses of
	   Stack. StackArray would implement a Stack using an array and
	   StackList would implement a Stack using a list. By virtue of
	   inheritance both implementations share the same interface and
	   hence objects of the two subclasses can be interchanged in
	   a program.

    B. Polymorphism: Each subclass is allowed to implement its methods
       differently, although the methods are supposed to have the same
       result. For example, the draw method is supposed to render an
       object on the screen but each subclass of a GraphicalObject will
       implement this method differently. Similarly, the pointInObj method
       should indicate whether a point intersects the object but each
       subclass may implement this method differently. In the object-oriented
       programming community, the term polymorphism is used to
       describe this ability to have a method accomplish a certain result
       but to be implemented in many different ways.

    C. Extending An Interface: A subclass is allowed to add to the
       interface it inherits but it is not allowed to subtract from it.
       For example, a Line class probably wants to add a setX1, setY1,
       setX2, and setY2 method to its interface so that a user can
       set its endpoints. Similarly a Text class wants to add a setString
       method that sets the string that a text object should display. 
       Finally a Bitmap class might add a loadFile method and a stretch
       method that allows a user to specify the bitmap that should be
       displayed and that allows a bitmap to be distorted. Since a subclass
       cannot subtract from its interface, it must implement all
       the methods that it inherits plus any methods it chooses to add to
       the interface. 

    D. Types and Subtypes

        1. A type is defined as a name and an interface that
	   provides a set of operations that can be performed on that
	   type. For example, an integer is a type that supports operations
	   such as +, -, *, / and %. The class GraphicalObject is a type
	   that supports the interface {draw, pointInObj, getBounds}).
	   
	   a. A type does not specify an implementation but it does specify
	      the outcome of its operations. 

	   b. Types pre-defined by the language are called primitive 
	      types. Types defined by the user are called user-defined
	      types. 

	2. A subtype is a type that extends another type's
	     interface. It implements all the methods defined by
	     the type, plus potentially one or more additional methods.
	     For example, a Line is considered a subtype of GraphicalObject. 
	     It implements the draw, pointInObj, and getBounds methods,
	     as well as other methods, such as setX1, setY1, setX2, and
	     set Y2.

	3. In general superclasses define types and subclasses define
	    subtypes.

II. Benefit of Inheritance: By permitting polymorphism, inheritance
    allows a group of related objects to be handled interchangeably, even
    though they are implemented differently. The reason they can be
    handled interchangeably is that the objects support the same operations
    and these operations will have the same outcome, regardless of how
    they are implemented. Specifically, objects may be handled
    interchangeably because a pointer to a class X may, in addition to 
    pointing to an object of class X, point to any object which is a subclass
    of X. For example, if we declare obj_ptr as follows:

        GraphicalObject* obj_ptr;

    then obj_ptr can point to either a GraphicalObject or a
    Rectangle, Line, Bitmap, or text object.

    A. The ability of a superclass pointer to also point to
	        subclass objects allows:

	1. related objects with different subclasses to be stored
		   in the same list, stack, tree, etc. For example a drawing
		   program can store a group of rectangles, lines, bitmaps,
		   and text objects in one list rather than having to store
		   them in four separate lists. The drawing program can
		   traverse the list and be confident that it can call the
		   same method, for example a draw method, on each element
		   in the list.

	2. any object which is an instance of a subclass of X to
		    be passed to a function expecting an instance of X.
		    For example, suppose we have the function declaration:

		    void addToClipRegion(GraphicalObject *a);

		    Not only can we pass GraphicalObjects to addToClipRegion, 
		    we can also pass Rectangles, Lines, Bitmaps, and Text 
		    objects to addToClipRegion. addToClipRegion might add
		    the objects bounding rectangle to an existing clip region
		    using the getBounds method. A window system uses a clip
		    region to only draw the parts of an object that intersect
		    the clip region. For example, if part of a window
		    becomes uncovered as a result of moving another window,
		    the window system only wants to draw the parts of objects
		    that were covered by the moved window. Hence it would
		    use a clip region equal in size to the moved window.
    B. The reason a pointer to a class may point to a subclass is
	        that it is guaranteed that any method implemented by
		the class is also implemented by the subclass. For example,
		when we declare obj_ptr to be of type GraphicalObject, we
		are permitted to invoke the methods draw,pointInObj, and
		getBounds. Since any subclass of GraphicalObject also 
		implements these three methods, it is safe to allow obj_ptr 
		to point to any subclass.

    C. If a pointer to class X points to an object of subclass Y, then
	        the pointer can only invoke methods in X's interface. Any
		additional methods defined by Y cannot be accessed through
		the pointer. For example, if obj_ptr points to a Line,
		it cannot invoke setX1 or setY1, since these
		methods are not in GraphicalObject's interface.

		i. You can gain access to the sub-types methods by 
		    downcasting. A downcast explicitly tells
		    the compiler that you know that the object currently
		    being pointed at has a particular type. A downcast
		    is a risky operation because if you are wrong, your
		    program will most likely core dump. Downcasting is
		    generally frowned upon, and is almost always
		    unnecessary. Typically if you know that a variable
		    points to an object with a particular type, you could 
		    have simply declared the object to have that type,
		    thus obviating the need for a downcast.

		    Example: I make the following definition and then
		     use a downcast to call the setX1 method:

		        GraphicalObject *g = new Line();
			((Line *)g)->setX1(10);

		     Since I know that g points to a line, I should
		     have declared g to be a line to start with. Then
		     I would not have had to do the downcast. In other
		     words, the following code is preferable:

		        Line *g = new Line();
			g->setX1(10);

    D. A pointer to a subclass may not point to a superclass object. For
       example, the compiler will flag the following statement:

       Line *l = new GraphicalObject();

       The reason for this restriction is that a superclass is not guaranteed
       to implement all of the subclass's methods. For example, when we
       declare l to be a pointer to Line, that means that l is entitled
       to call the setX1 method. However, a GraphicalObject does not
       implement the setX1 method. Hence l cannot point to a GraphicalObject.

III. Syntax 

    A. To derive a class from another class, use the following
         syntax:

	 class subclass_name : public super_class_name { ... }

         For example:

	    class Rectangle : public GraphicalObject { ... }

	 1. A super class is also called a base class
	 2. A sub class is also called a derived class
	 3. If you forget to use the keyword public, then C++ will
	     use private by default. This will cause problems that you
	     don't want to deal with unless you are an expert C++ user.
	     Basically, public means you inherit the interface and
	     the implementation of the super class, while private means
	     you inherit the implementation but not the interface. For
	     more details, see the book.

    B. Virtual methods: Indicates that sub classes will
	specialize this method. 

	1. Syntax: Affix the keyword virtual to a method declaration:

		class GraphicalObject {
		    virtual void draw();
		};

	2. A method is virtual if it is declared virtual or if
		a super class declares it virtual

	3. A method's signature in the subclass must exactly match
	        the method's signature in the super class for the
		method to be considered the same as the method in
		the super class.

	    a. If you alter the number of parameters, the method will
	        be considered different (it will be considered to overload
		the method rather than to redefine the method).

	    b. You can never alter the return type of a virtual method.
	        The compiler will not consider the altered method to be
		a different method, it will simply consider it to be an
		error.

		Example:

		   class Stack {
		       virtual void pop ();
		       virtual push(void *);
		   };

		   class IntStack {
		       void     pop();    // overrides Stack's pop method
		       int      pop();    // error: cannot alter return type
		       void     push(int); // overloads push
		   };

		   Stack *stack;
		   
		   stack = new IntStack();
		   stack->push(10);  // error: Stack does not have a push
		                     // method that takes an int
                   stack->pop();     // calls IntStack's void pop method

	    c. The restriction on not being able to overload the return
	       types makes it impossible to define a generic data structure
	       and then have subclasses specialize it for specific types
	       like ints, strings, etc. To overcome this difficulty C++
	       has a concept called templates which we will briefly cover
	       later in the course.

	3. There are three different categories of implementation inheritance

	    a. Non-virtual method: If a method is declared non-virtual,
	        then the designer's intention is that the method's 
		implementation be inherited by all sub-classes.

	    b. Virtual method with a default implementation: if a method
	        is declared virtual and given a default implementation,
		then the method's implementation will be inherited unless
		a subclass overrides the implementation.

	    c. Pure virtual method: If a method is declare virtual and not
	        given a default implementation, then all subclasses must
		define the implementation. 

		i. A pure virtual method is declared by appending "= 0" to
		   the declaration:

		   virtual void draw() = 0;

		ii. If any methods in a class are declared pure virtual, then
		    the class is considered an abstract super class.
		    Concrete instances of the class cannot be created at
		    run-time, because the specification of the class is
		    incomplete.

	4. A virtual declaration indicates to C++ that it must
	    wait until run-time to determine which method it will use.

	    Example:

	        class GraphicalObject {
		    virtual void draw();
		    void         set_bounding_box(int, int, int, int);
		};

		class Rectangle: public GraphicalObject {
		    void         draw();
		    // you should not do this because set_bounding_box
		    // was not declared virtual. I am doing this for
		    // illustrative purposes only.
		    void	 set_bounding_box(int, int, int, int);
		};

		GraphicalObject* obj_ptr;

		obj_ptr = new GraphicalObject;
		obj_ptr->draw(); // calls the draw method for GraphicalObject

		obj_ptr = new Rectangle;
		obj_ptr->draw(); // calls the draw method for Rectangle

		// calls the set_bounding_box method for GraphicalObject 
		// because set_bounding_box is not declared virtual. Hence
		// the call will be resolved at compile time. Since obj_ptr
		// points to GraphicalObject, the call resolves to
		// GraphicalObject's set_bounding_box method

		obj_ptr->set_bounding_box(10, 10, 40, 40); 

	5. When to declare an inherited method and when not to declare
	   an inherited method in a subclass.

	   a. If you decide to provide an implementation for an inherited 
	      method in a subclass, then you must declare the inherited
	      method in that subclass, even though you also declared it in the
	      superclass.

	   b. If you do not provide an implementation for an inherited
	      method (because you are inheriting its implementation
	      from a superclass), then you must not declare the
	      method in the subclass.

	 6. Make destructors virtual in super classes

	        a. Unlike other virtual methods, destructors have different
		    names (e.g., ~GraphicalObject and ~Rectangle). Nonetheless,
		    C++ considers destructors to be the same method.
		    Consequently, you must declare them virtual if you
		    want the right destructor to be called.

		b. Unlike other virtual methods, C++ will call destructors
		    all the way up the inheritance hierarchy (e.g., if it calls
		    Rectangle's destructor, it will also call GraphicalObject's
		    destructor).

		c. You cannot declare a destructor to be pure virtual. It
		    must have a default implementation, even if it is only
		    {}. For example:

		    virtual ~GraphicalObject() = 0;  // won't work!!
		    virtual ~GraphicalObject() {}    // fine

		    If you declare a destructor pure virtual, or you simply
		    forget to provide a default implementation, the compiler
		    won't complain but the linker will. 

		d. If you see a linker error about __NumberClassName
		    (e.g., __14ExpressionNode), you have forgotten
		    to define an implementation for a destructor or
		    a constructor

		    Example: If I forget to include a definition for
		      ~GraphicalNode(), I get the linker errors:

		      Undefined                       first referenced
		       symbol                             in file
		       __14GraphicalNode                /var/tmp/cca004XC1.o
		       _._14GraphicalNode               /var/tmp/cca004XC1.o

IV. Some Thoughts on Defining Class Hierarchies

    A. Inheritance should be used to share interfaces, not implementation.
        It is nice if you luck out and get to share some implementation
	but your only consideration in deciding whether to use inheritance
	is whether the subclasses share a common interface.

    B. In this class we will primarily use inheritance when we need
        to store similar types of objects (e.g., events in a simulation,
	graphical objects in a user interface) in a data structure,
	such as a priority queue, stack, etc.

    C. If you want to share implementation, it is often better to use
        object composition rather than inheritance. For example, if
	you want a stack to be implemented in terms of an array, it
	is better to define a stack to have a pointer to an array object
	than to define a stack to inherit from an array object. The
	reason that a stack should not inherit from an array is that
	the interfaces for the two objects are different. For example,
	the extendible StringArray presented earlier in the semester
	supports the methods get_value and set_value. You do not want
	a Stack to support these two methods so you should not have
	a Stack inherit from StringArray. Instead you should make the
	Stack have a StringArray object. To emphasize the distinction between
	the inheritance of interfaces and the re-use of implementation.
	the object-oriented programming community defines is-a
	and has-a relationships. 

	1. An is-a relationship means that two classes have a shared 
	    interface. In other words, one class is a subclass of the
	    other class.

	2. A has-a relationship means that one class uses another class
	    as a part. It often means that one class uses the implementation
	    of the other class.