[PDF] Encapsulation + Inheritance + Polymorphism
[PDF] Encapsulation + Inheritance + Polymorphism
[PDF] Encapsulation + Inheritance + Polymorphism
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