20.04.2014 Views

[PDF] Encapsulation + Inheritance + Polymorphism

[PDF] Encapsulation + Inheritance + Polymorphism

[PDF] Encapsulation + Inheritance + Polymorphism

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

<strong>Inheritance</strong> and <strong>Polymorphism</strong><br />

CS 355<br />

March 1, 2006<br />

1. Object Oriented Programming (OOP)<br />

2. <strong>Inheritance</strong><br />

• Motivation 1: Software reuse<br />

OOP = ADT + inheritance + polymorphism<br />

– Given: An ADT that has 90% of the functionality you want.<br />

– Solution: Create a new ADT that inherits the functionality of the given ADT plus adds the<br />

remaining features.<br />

– Example: Say we are given a ADT Point that encapsulates the notion of a location in 2D.<br />

class Point {<br />

protected:<br />

double x, y;<br />

public:<br />

Point(double xx = 0.0, double yy = 0.0) : x(xx), y(yy) {}<br />

double getx() const {return x;}<br />

double gety() const {return y;}<br />

void move(double x, double y) {this->x = x, this->y = y;}<br />

void moveRel(double x, double y) {this->x += x; this->y += y;}<br />

void draw() const {glBegin(GL_POINTS); glVertex2f(x,y); glEnd();}<br />

};<br />

Say we want to create a Circle ADT that allows us to represent circles centered at various<br />

locations. We can “inherit” the functionality we want from the Point ADT and add the extra<br />

stuff needed to represent a circle.<br />

class Circle : public Point {<br />

protected:<br />

double rad;<br />

public:<br />

Circle(double x = 0.0, double y = 0.0, double r = 1.0) : Point(x,y), rad(r) {}<br />

static const double pi;<br />

double area() const {return pi*rad*rad;}<br />

void draw() const;<br />

};<br />

const double Circle::pi = 3.1415926535;<br />

void Circle::draw() const {<br />

// insert code to draw a circle centered at (x,y) with radius ’rad’<br />

}<br />

1


The Circle class inherited all the fields and methods of the Point class. Therefore we can<br />

create an instance of Circle and invoke all the inherited methods (e.g., move) plus the newly<br />

defined methods (e.g., area) on that instance.<br />

∗ We call Point the superclass or the base class. We call Circle the subclass or the derived<br />

class.<br />

∗ Note that a Circle is a Point, but not vice versa.<br />

∗ Note the new keyword protected. This is synonymous with private for peer classes,<br />

but is like public for derived classes.<br />

∗ Note the new notation for invoking the superclass’s constructor. It is often critical for<br />

all superclasses to be completely initialized before any derived classes! Destructors are<br />

invoked in reverse order (why?).<br />

• Motivation 2: Program organization<br />

– Without inheritance, all ADT’s are independent peer entities which is not always a good fit<br />

for the problem space being addressing.<br />

– With inheritance we can create hierarchies of ADT’s more accurately model the commonality<br />

between different types.<br />

• Overriding behavior<br />

3. <strong>Polymorphism</strong><br />

– Not only can we add functionality in a derived class, we can override certain behaviors defined<br />

in the super class. This is accomplished by redefining inherited methods in the derived class.<br />

Note the behavior with regards to the draw method has been overridden by the Circle class.<br />

• <strong>Polymorphism</strong> (literally “many forms”) means one type can have many forms.<br />

• Example: Polymorphic shapes<br />

– We create a base class Shape that can take on many forms (it can behave like a point, a circle,<br />

or a rectangle).<br />

– A shape is really an abstract concept.<br />

Question: What is the area of a shape?<br />

Answer: It depends on what kind of shape it is.<br />

– The polymorphic methods of Shape are area and draw. In C++ we call these “virtual<br />

methods” and tag them with the keyword virtual.<br />

class Shape {<br />

protected:<br />

int x, y; // location<br />

public:<br />

Shape (int xx = 0, int yy = 0) : x(xx), y(yy) {}<br />

int getx() const {return x;}<br />

int gety() const {return y;}<br />

void move(int x, int y) {this->x = x, this->y = y;}<br />

void moveRel(int x, int y) {this->x += x; this->y += y;}<br />

virtual double area() const = 0;<br />

virtual void draw() const = 0;<br />

};<br />

// pure virtual method (no implementation)<br />

// pure virtual method<br />

2


– Note that the Shape class specifically states that it does not implement the area and draw<br />

methods (the syntax implies that their implementation is null). These type of methods are<br />

called pure virtual methods.<br />

– Classes that contain pure virtual methods can not be instantiated (why?). We call these<br />

Abstract classes.<br />

– The motivation for creating such a class is to isolate all the features that shapes have in<br />

common and define them exactly once! What are the benefits to doing this?<br />

– Here we created a concrete type called Rectangle<br />

class Rectangle : public Shape {<br />

int w, h;<br />

public:<br />

Rectangle(int x = 0, int y = 0, int w = 1, int h = 1) : Shape(x,y) {<br />

this->w = w; this->h = h;<br />

}<br />

virtual double area() const {return w*h;}<br />

virtual void draw() const;<br />

};<br />

void Rectangle::draw() const {<br />

glBegin(GL_LINE_LOOP);<br />

glVertex2i(x, y);<br />

glVertex2i(x+w, y);<br />

glVertex2i(x+w, y+h);<br />

glVertex2i(x, y+h);<br />

glEnd();<br />

}<br />

– Since Rectangle is concrete, we can actually create instances of type Rectangle:<br />

Shape *s = new Rectangle(10, 10, 50, 50); // legal: concrete class<br />

Shape *s = new Shape(10,10); // illegal!! abstract class<br />

– Here we create another concrete class Circle:<br />

class Circle : public Shape {<br />

int radius;<br />

public:<br />

Circle(int x = 0, int y = 0, int rad = 1) : Shape(x,y) {radius = rad;}<br />

static const double pi;<br />

virtual double area() const {return pi*radius*radius;}<br />

virtual void draw() const;<br />

};<br />

const double Circle::pi = 3.1415926;<br />

void Circle::draw() const {<br />

glPushMatrix();<br />

glTranslatef(double(x),double(y),0);<br />

glBegin(GL_LINE_LOOP);<br />

const int numlines = 100;<br />

const double dtheta = 2*pi/numlines;<br />

int i; double theta;<br />

for (i = 0, theta = 0.0; i < numlines; i++, theta += dtheta)<br />

glVertex2d(radius*cos(theta), radius*sin(theta));<br />

glEnd();<br />

glPopMatrix();<br />

}<br />

3


– Since Shape is abstract, we can not create an array of Shape’s, but we can create an an array<br />

of pointers to Shape’s What is the practical reason for this?<br />

const int numshapes = 3;<br />

Shape *shapes[numshapes];<br />

...<br />

shapes[0] = new Rectangle(10, 10, 50, 50);<br />

shapes[1] = new Rectangle(300, 10, 50, 200);<br />

shapes[2] = new Circle(300,300, 100);<br />

...<br />

for (int i = 0; i < numshapes; i++)<br />

shapes[i]->draw();<br />

• These examples demonstrate both interface inheritance, and implementation inheritance.<br />

• Interface inheritence in C++, Objective-C, and Java<br />

– Example: Say we want to insert objects into a priority queue which requires that each object<br />

has a priority() method as defined by the following abstract class:<br />

class Prioritorizable {<br />

public:<br />

virtual int priority() const = 0;<br />

};<br />

The interface for PriorityQueue could look like this:<br />

class PriorityQueue {<br />

...<br />

public:<br />

...<br />

void insert(Prioritorizable *elem);<br />

Prioritorizable *deleteMin();<br />

};<br />

Here we define a concrete subclass of Prioritorizable called Widget:<br />

class Widget : public Prioritorizable {<br />

...<br />

int p;<br />

public:<br />

virtual int priority() const {return p;}<br />

};<br />

Now we can insert instances of Widget into PriorityQueue’s. Say we also want to insert<br />

intances of Widget into a hash table that requires Widget to be a subclass of the abstract<br />

class Hashable:<br />

class Hashable {<br />

public:<br />

virtual long int hash() const = 0;<br />

};<br />

To solve this problem in C++, we must use multiple inheritance by defining Widget to be a<br />

subclass of both Prioritorizable and Hashable:<br />

class Widget : public Prioritorizable, public Hashable {<br />

...<br />

public:<br />

virtual int priority() const { ... }<br />

virtual long int hash() const = { ... }<br />

};<br />

4


This is probably the least objectionable use of multiple inheritence in C++, because we<br />

are simple inheriting mutiple interfaces which is not as troublesome as inheriting multiple<br />

implementations (why might this be problamatic?).<br />

– Objective-C allows only single inheritence, but allows the programmer to define a class that<br />

conforms to mutiple protocols:<br />

@protocol Prioritorizable<br />

-(int) priority;<br />

@end<br />

@protocol Hashable<br />

-(long int) hash;<br />

@end<br />

@interface Widget : NSObject<br />

...<br />

-(int) priority;<br />

-(long int) hash;<br />

@end<br />

Here Widget is a subtype of NSObject, but conforms to the protocols Prioritorizable and<br />

Hashable. At runtime, we can check to see if an object conforms to some particular protocol:<br />

if ([obj conformsTo:@protocol(Hashable)]) ...<br />

– Java also only allows single inheritence, but borrowed Objective-C’s notion of protocol’s. In<br />

Java a protocol is termed interface (not the same as Objective-C’s interfaces):<br />

public interface Prioritorizable {<br />

public int priority();<br />

}<br />

public interface Hashable {<br />

public long int hash();<br />

}<br />

public class Widget extends Object implements Prioritorizable, Hashable {<br />

...<br />

public int priority() { ... }<br />

public long int hash() { ... }<br />

}<br />

4. Virtual Function Tables and Dynamic Dispatch<br />

• Note that when we specified the draw() method in our Shape class we used the keyword virtual.<br />

This specifies that the actual draw() method that is called is not known until runtime:<br />

Shape *shape;<br />

...<br />

shape->draw();<br />

// Which draw method is called?<br />

Non-virtual method calls in C++ are statically linked to the appropriate function body:<br />

shape->move(1, 2);<br />

// Use Shape’s move() method (shape in not virtual)<br />

Polymorphic operators (i.e., virtual methods) are implemented in C++ by using a look-up table<br />

(called a virtual function table) attached to each object that maps method signatures to function<br />

addresses at runtime.<br />

5


• All methods in Java and Objective-C are “virtual.” Objective-C uses a much more sophisticated<br />

technique called dynamic dispatch which we will talk about later.<br />

6

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!