software design and c++ lecture 5 inheritance and polymorphism

50
Software Design and C+ + Lecture 5 Inheritance and Polymorphism

Post on 22-Dec-2015

218 views

Category:

Documents


0 download

TRANSCRIPT

Software Design and C++

Lecture 5

Inheritance and Polymorphism

Contents

• Introduction• Base classes and derived classes• Initializing derived class objects • Protected class members• Towards polymorphism – pointers and

references to objects• Polymorphism• Abstract classes• Polymorphism and OOP

Introduction• Inheritance is a fundamental requirement of oriented

programming

• It allows us to create new classes by refining existing classes

• Essentially a derived class can inherit data members of a base class

– The behaviour of the derived class can be refined by redefining base class member functions or adding new member function

– A key aspect of this is polymorphism where a classes behaviour can be adapted at run-time

Base classes and derived classes

• We can think of many examples in real life of how a (base) class can be refined to a set of (derived) classes

• For example a Polygon class can be refined to be a Quadrilateral which can be further refined to be a Rectangle

• We can think of these classes as following an IS-A relationship– A Quadrilateral IS-A Polygon– A Rectangle IS-A Quadrilateral

• We can think of lots of other possible examples of base/derived classes

Base class Derived class

Shape Triangle, Circle, Rectangle

Account Current, Deposit

Student Undergraduate, Postgaduate

Vehicle Car, Truck, Bus

Filter Lowpass, Bandpass, Highpass

Example• An Account base class models basic information about a bank account

– Account holder

– Account number

– Current balance

• Basic functionality– Withdraw money

– Deposit money

class Account class Account {{

private:private:int account_number;int account_number;char* account_holder;char* account_holder;int balance;int balance;

public:public:Account(int, char*,int);Account(int, char*,int);

void withdraw(int amount) void withdraw(int amount) { {

if (balance>amount)if (balance>amount)balance-=amount;balance-=amount;

}}

void deposit(int amount) { balance+=amount;}void deposit(int amount) { balance+=amount;}};};

• We can consider refinements to our Account class

– CurrentAccount

• Can have an overdraft facility

• No interest paid

– DepositAccount

• Pays interest on any balance

• No overdraft facility

• We will create our refined classes using public inheritance from the Account base class

• Classes CurrentAccount and DepositAccount inherit the basic attributes (private members) of account

– account_number

– account_holder

– balance

• Also, new attributes are added

– CurrentAccount::overdraft_facility

– DepositAccount::interest_rate

class CurrentAccount public Accountclass CurrentAccount public Account{{

private:private:int overdraft_facility;int overdraft_facility;

public:public:CurrentAccount(int, char*, int, int);CurrentAccount(int, char*, int, int);

void withdraw(int); // Takes account of overdraft void withdraw(int); // Takes account of overdraft // facility// facility

};};

class DepositAccount public Accountclass DepositAccount public Account{{

private:private:float interest_rate;float interest_rate;

public:public:DepositAccount(int, char*, int, float);DepositAccount(int, char*, int, float);

float calc_interest(); float calc_interest(); // Calculates interest based // Calculates interest based // on the current balance// on the current balance

};};

CurrentAccount

account_numberaccount_holderbalance

deposit()withdraw()

DepositAccount

overdraft_facility

withdraw()

interest_rate

calc_interest()

account_numberaccount_holderbalance

deposit()withdraw()

• In class CurrentAccount, the member function withdraw() is overridden– This will be a crucial aspect of object

oriented programming as we shall see later• Polymorphism

• Also, a new member function, calc_interest() is defined in DepositAccount– This is an example of a derived class

extending a base class

• Before we can finish implementing the derived classes, we need to consider private/public access

• Key point 1

– In public inheritance, public member functions of the base class become public member functions of the derived class

• Thus in our example, the following are public member functions and can be called from outside of the class– CurrentAccount::withdraw()– CurrentAccount::deposit()– DepositAccount::withdraw()– DepositAccount::deposit()

• Key point 2– Private members of the base class can not be accessed from the derived class

• Obvious otherwise encapsulation could be easily broken by inheriting from the base class

• Begs the question, how do we initialise derived class objects?

• We can see how this is done by implementing the constructors of DepositAccount and CurrentAccount

Account::Account(int acc_no, char* acc_holder, int b)Account::Account(int acc_no, char* acc_holder, int b){{

account_number=acc_no;account_number=acc_no;strcpy(acc_holder,account_holder);strcpy(acc_holder,account_holder);balance=b;balance=b;

}}

DepositAccount::DepositAccount(int acc_no, char* DepositAccount::DepositAccount(int acc_no, char* acc_holder, int b, float int_rate):Account(acc_no, acc_holder, int b, float int_rate):Account(acc_no, acc_holder, b) acc_holder, b) {interest_rate=int_rate;}{interest_rate=int_rate;}

CurrentAccount::CurrentAccount(int acc_no, char* CurrentAccount::CurrentAccount(int acc_no, char* acc_holder, int b, int ov_facility):Account(acc_no, acc_holder, int b, int ov_facility):Account(acc_no, acc_holder, b) {overdraft_facility=ov_facility;}acc_holder, b) {overdraft_facility=ov_facility;}

• Thus derived class constructors are implemented by first calling the base class constructor and then initializing specific derived class private data members

• Uses the following syntax:MyDerivedClass:MyDerivedClass(int arg1, int arg2, int MyDerivedClass:MyDerivedClass(int arg1, int arg2, int arg3):MyBaseClass(arg1, arg2)arg3):MyBaseClass(arg1, arg2){{

// Initialize additional private data member using arg3// Initialize additional private data member using arg3}}

Protected class members

• A protected class member is one that can be accessed by public member functions of the class as well as public member functions of any derived class– Its half way between private and public

– Encapsulation is then broken for classes in the inheritance hierarchy and thus must be used where performance issues are critical

• Consider the implementation of CurrentAccount::withdraw() and DepositAccount::calc_interest()

void CurrentAccount::withdraw(int amount)void CurrentAccount::withdraw(int amount){{

// Needs to access and update Account::balance // Needs to access and update Account::balance }}

float DepositAccount::calc_interest()float DepositAccount::calc_interest(){{

// Needs to access and update Account::balance// Needs to access and update Account::balance}}

• Making Account::balance protected removes the need for Account::get_balance() and Account::set_balance() access functions

class Account class Account {{

private:private:int account_number;int account_number;char* account_holder;char* account_holder;

protected:protected:int balance;int balance;

public:public:Account(int, char*,int);Account(int, char*,int);void withdraw(int amount) { balance-=amount;}void withdraw(int amount) { balance-=amount;}void deposit(int amount) { balance+=amount;}void deposit(int amount) { balance+=amount;}

};};

void CurrentAccount::withdraw(int amount)void CurrentAccount::withdraw(int amount){{

if ((balance-amount)>-overdraft_facility)if ((balance-amount)>-overdraft_facility)balance-=amount;balance-=amount;

}}

float DepositAccount::calc_interest()float DepositAccount::calc_interest(){{

float interest=balance*interest_rate;float interest=balance*interest_rate;balance+=interest;balance+=interest;return interest;return interest;

}}

• We can summarise private/protected/public access in the following table

Class member Can be accessed from

private public member functions of same class

protected public member functions of same class and derived classes

public Anywhere

Towards polymorphism – pointers and references to objects

• A key aspect of object oriented programming is polymorphism – The ability to reference objects which show

different behaviour

• Polymorphism is implemented through pointers and references to objects

• We can set up a pointer (or a reference) to an object very easily

Account acc(12345, “J. Smith”, 100);Account acc(12345, “J. Smith”, 100);

Account* p_acc=&acc;Account* p_acc=&acc;

p_acc

Account

12345J Smith100

deposit()withdraw()

• Key point

– Because of the IS-A relationship, we can point a base class pointer at derived class objects – but not vice versa

– An Account object is a DepositAccount and a CurrentAccount object

DepositAccount dep_acc(12345, “J. Smith”, 100,5.0);DepositAccount dep_acc(12345, “J. Smith”, 100,5.0);

Account* p_acc = &dep_acc;Account* p_acc = &dep_acc; //OK//OK

p_acc

DepositAccount

5.0

calc_interest()

12345J Smith100

deposit()withdraw()

• But we can’t point a derived class pointer at a base class object

– An Account object is not (necessarily) a DepositAccount object

Account acc(12345, “J. Smith”, 100);Account acc(12345, “J. Smith”, 100);

DepositAccount* p_dep_acc = &acc;DepositAccount* p_dep_acc = &acc; //Error!//Error!

Polymorphism

• Polymorphism is the key concept in object oriented programming

• First we will look at the mechanics of polymorphism using our simple example classes

• We will then look at how polymorphism lends itself to OOP as well as the advantages over a procedural approach

• We can see from the CurrentAccount class that CurrentAccount::withdraw() overrides Account::withdraw()

CurrentAccount

account_numberaccount_holderbalance

deposit()withdraw()

overdraft_facility

withdraw()

overridden

• Consider the following code

CurrentAccount curr_acc(12345, “J. Smith”, 100,500);CurrentAccount curr_acc(12345, “J. Smith”, 100,500);

Account* p_acc = &curr_acc;Account* p_acc = &curr_acc; //OK//OK

p_acc->withdraw(250);p_acc->withdraw(250); // Which // Which withdraw()withdraw()

account_numberaccount_holderbalance

deposit()withdraw()

overdraft_facility

withdraw()

p_acc

CurrentAccount

Which one is called?

• Because p_acc has been declared as Account*, Account::withdraw() is called

• Incorrect behaviour as p_acc points to a CurrentAccount object

CurrentAccount curr_acc(12345, “J. Smith”, 100, 500);CurrentAccount curr_acc(12345, “J. Smith”, 100, 500);

Account* p_acc = &curr_acc;Account* p_acc = &curr_acc;

p_acc->withdraw(250);p_acc->withdraw(250); // Calls // Calls Account::withdraw()Account::withdraw()// Ignores overdraft facility// Ignores overdraft facility

• The solution is to make Account::withdraw() a virtual function so that dynamic binding takes place

– The derived class member function CurrentAccount::withdraw() is actually called and correct behaviour is observed

• Changes to the definition of the Account class are minimal

class Account class Account {{

private:private:int account_number;int account_number;char* account_holder;char* account_holder;

protected:protected:int balance;int balance;

public:public:Account(int, char*,int);Account(int, char*,int);

virtualvirtual void withdraw(int amount) void withdraw(int amount) { balance-=amount;}{ balance-=amount;}

void deposit(int amount) { balance+=amount;}void deposit(int amount) { balance+=amount;}};};

Abstract classes

• In our example classes, Account::withdraw() was declared as a virtual function– Were were able to provide a sensible

implementation of this function– This implementation could be regarded as

default behaviour if the function was not overridden in derived classes

CurrentAccount curr_acc(12345, “J. Smith”, 100, 500);CurrentAccount curr_acc(12345, “J. Smith”, 100, 500);

DepositAccount dep_acc(56789, “J. Smith”, 100, 5.0);DepositAccount dep_acc(56789, “J. Smith”, 100, 5.0);

Account* p_acc_curr = &curr_acc;Account* p_acc_curr = &curr_acc;

Account* p_acc_dep = &dep_acc;Account* p_acc_dep = &dep_acc;

p_acc_curr->withdraw(250);p_acc_curr->withdraw(250); // // Account::withdraw()Account::withdraw() // overridden by// overridden by // CurrentAccount::withdraw()// CurrentAccount::withdraw()

p_acc_dep->withdraw(250);p_acc_dep->withdraw(250); // // Account::withdraw()Account::withdraw() // called// called

• Abstract classes arise when there is no sensible implementation of the virtual functions in the base class– Base class virtual functions are always overridden

by derived class implementations

• In this case, we simply assign the base class virtual functions to zero– They become pure virtual functions

– A class containing at least one pure virtual function is an abstract class

• As an example, suppose we wanted to design a hierarchy of shape classes for a computer graphics application

class Shape class Shape {{

public:public:virtual void draw()=0;virtual void draw()=0;virtual void move(int x, int y)=0;virtual void move(int x, int y)=0;virtual float area()=0;virtual float area()=0;

};};

• Shape is an abstract concept and there are no sensible definitions of Shape::draw(), Shape::move() and Shape::area()

– They are assigned to zero and hence are pure virtual functions

– This makes Shape an abstract class

– We cannot declare Shape objects

• Derived objects of Shape can be created and implementations of the pure virtual functions provided

class Square : public Shapeclass Square : public Shape{{

private:private:int x_pos, y_pos;int x_pos, y_pos;int side;int side;

  public:public:

Square(int x, int y, int length)Square(int x, int y, int length){ x_pos=x; y_pos=y; side=length;}{ x_pos=x; y_pos=y; side=length;}

void draw() void draw() {// Call graphics commands …}{// Call graphics commands …}

void move(int x, int y)void move(int x, int y){ x_pos+=x; y_pos+=y;}{ x_pos+=x; y_pos+=y;}

float area() { return side*side};float area() { return side*side};};};

class Circle : public Shapeclass Circle : public Shape{{

private:private:int x_cent, y_cent;int x_cent, y_cent;int radius;int radius;

  public:public:

Circle(int x, int y, int r)Circle(int x, int y, int r){ x_cent=x; y_cent=y; radius=r;}{ x_cent=x; y_cent=y; radius=r;}

void draw() void draw() {// Call graphics commands …}{// Call graphics commands …}

void move(int x, int y)void move(int x, int y){ x_cent+=x; y_cent+=y;}{ x_cent+=x; y_cent+=y;}

float area() { return 3.14159*radius*radius;}float area() { return 3.14159*radius*radius;}};};

• The pure virtual functions in Shape are always overridden in the derived class through polymorphism

void main() void main() {{

Shape s;Shape s; // Error! Can’t create a // Error! Can’t create a ShapeShape object object

Shape* sp=new Square(5,6,20);Shape* sp=new Square(5,6,20); // OK// OK

sp->move(10,10);sp->move(10,10); // Calls // Calls Square::move()Square::move()

sp->draw();sp->draw(); // Calls // Calls Square::draw()Square::draw()

float a=sp->area();float a=sp->area(); // Calls // Calls Square::area()Square::area()};};

• We can regard abstract classes as a ‘glue’ which binds related classes together and where we don’t have to worry about implementational details– They just have to present a common interface

Shape

Circle Square Triangle

draw()move()area()

Polymorphism and OOP

• We can easily conceive of an application where a heterogenous list (or array) of shape objects are manipulated

• For example, in a CAD system, a complex drawing may comprise a list of basic shapes

• Each object on the list may exhibit different behaviour (for example may draw itself differently) but the application user need not worry about this

– Polymorphism looks after it

• Example– A hetereogenous list of shapes (accessed an

array of Shape pointers) can each be drawn• This could be a means of drawing a complex shape from

simpler sub-shapes

Shape** s s[0] s[1] …..s[2] s[3] s[4]

draw() draw() draw()draw()draw()

void main() void main() {{

Shape** s = new Shape*[5];Shape** s = new Shape*[5];s[0]=new Square(0,0,5);s[0]=new Square(0,0,5);s[1]=new Circle(2,4,3);s[1]=new Circle(2,4,3);s[2]=new Circle(3,1,6);s[2]=new Circle(3,1,6);s[3]=new Square(2,5,3);s[3]=new Square(2,5,3);s[4]=new Square(2,6,7);s[4]=new Square(2,6,7);

for (int j=0; j<5; j++)for (int j=0; j<5; j++)s[j]->draw();s[j]->draw(); // Draws each shape on the list// Draws each shape on the list

};};

• So what’s the advantage of doing it this (OOP) way?

• Suppose we want to extend our list of ‘sub-shapes’– We simply derive any new shape from Shape and

add the required functionality to the virtual functions draw(), move() and area()

– The code for the for loop doesn’t change

– Polymorphism automatically invokes the correct behaviour

class Triangle : public Shapeclass Triangle : public Shape{{

private:private:int x_vertices[3], int y_vertices[3];int x_vertices[3], int y_vertices[3];  

public:public:Triangle(int[] xv, int[] yv) {…}Triangle(int[] xv, int[] yv) {…}void draw() {…}void draw() {…}void move(int x, int y) {…}void move(int x, int y) {…}float area() {..}float area() {..}

};};

void main()void main(){{

…… for (int j=0; j<5; j++)for (int j=0; j<5; j++)

s[j]->draw();s[j]->draw(); // Draws each shape on the list// Draws each shape on the list}}

And finally…..

• Polymorphism is the critical component in object oriented systems

• C++ supports this with the virtual function– In Java, all inherited member functions are virtual

• Its important to understand how this leads to extendible applications as new objects exhibiting new behaviours can be easily introduced