1 / 50

Software Design and C++

Learn about the basics of inheritance and polymorphism in C++ programming, including creating derived classes and refining base class behaviors. Explore real-life examples and implementation details.

Download Presentation

Software Design and C++

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Software Design and C++ Lecture 5 Inheritance and Polymorphism

  2. 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

  3. 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

  4. 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

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

  6. Example • An Account base class models basic information about a bank account • Account holder • Account number • Current balance • Basic functionality • Withdraw money • Deposit money

  7. class Account { private: int account_number; char* account_holder; int balance; public: Account(int, char*,int); void withdraw(int amount) { if (balance>amount) balance-=amount; } void deposit(int amount) { balance+=amount;} };

  8. 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

  9. 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

  10. class CurrentAccount public Account { private: int overdraft_facility; public: CurrentAccount(int, char*, int, int); void withdraw(int); // Takes account of overdraft // facility };

  11. class DepositAccount public Account { private: float interest_rate; public: DepositAccount(int, char*, int, float); float calc_interest(); // Calculates interest based // on the current balance };

  12. CurrentAccount DepositAccount account_number account_holder balance account_number account_holder balance deposit() withdraw() deposit() withdraw() overdraft_facility interest_rate withdraw() calc_interest()

  13. 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

  14. 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

  15. 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()

  16. 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

  17. Account::Account(int acc_no, char* acc_holder, int b) { account_number=acc_no; strcpy(acc_holder,account_holder); balance=b; } DepositAccount::DepositAccount(int acc_no, char* acc_holder, int b, float int_rate):Account(acc_no, acc_holder, b) {interest_rate=int_rate;} CurrentAccount::CurrentAccount(int acc_no, char* acc_holder, int b, int ov_facility):Account(acc_no, acc_holder, b) {overdraft_facility=ov_facility;}

  18. 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 arg3):MyBaseClass(arg1, arg2) { // Initialize additional private data member using arg3 }

  19. 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

  20. Consider the implementation of CurrentAccount::withdraw() and DepositAccount::calc_interest() void CurrentAccount::withdraw(int amount) { // Needs to access and update Account::balance } float DepositAccount::calc_interest() { // Needs to access and update Account::balance }

  21. Making Account::balance protected removes the need for Account::get_balance() and Account::set_balance() access functions class Account { private: int account_number; char* account_holder; protected: int balance; public: Account(int, char*,int); void withdraw(int amount) { balance-=amount;} void deposit(int amount) { balance+=amount;} };

  22. void CurrentAccount::withdraw(int amount) { if ((balance-amount)>-overdraft_facility) balance-=amount; } float DepositAccount::calc_interest() { float interest=balance*interest_rate; balance+=interest; return interest; }

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

  24. 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

  25. We can set up a pointer (or a reference) to an object very easily Account acc(12345, “J. Smith”, 100); Account* p_acc=&acc; Account 12345 J Smith 100 p_acc deposit() withdraw()

  26. 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); Account* p_acc = &dep_acc; //OK

  27. DepositAccount 12345 J Smith 100 p_acc deposit() withdraw() 5.0 calc_interest()

  28. 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); DepositAccount* p_dep_acc = &acc; //Error!

  29. 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

  30. We can see from the CurrentAccount class that CurrentAccount::withdraw() overrides Account::withdraw() CurrentAccount account_number account_holder balance deposit() withdraw() overridden overdraft_facility withdraw()

  31. Consider the following code CurrentAccount curr_acc(12345, “J. Smith”, 100,500); Account* p_acc = &curr_acc; //OK p_acc->withdraw(250); // Which withdraw()

  32. CurrentAccount p_acc account_number account_holder balance deposit() withdraw() Which one is called? overdraft_facility withdraw()

  33. 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); Account* p_acc = &curr_acc; p_acc->withdraw(250); // Calls Account::withdraw() // Ignores overdraft facility

  34. 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

  35. class Account { private: int account_number; char* account_holder; protected: int balance; public: Account(int, char*,int); virtual void withdraw(int amount) { balance-=amount;} void deposit(int amount) { balance+=amount;} };

  36. 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

  37. CurrentAccount curr_acc(12345, “J. Smith”, 100, 500); DepositAccount dep_acc(56789, “J. Smith”, 100, 5.0); Account* p_acc_curr = &curr_acc; Account* p_acc_dep = &dep_acc; p_acc_curr->withdraw(250); // Account::withdraw() // overridden by // CurrentAccount::withdraw() p_acc_dep->withdraw(250); // Account::withdraw() // called

  38. 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

  39. As an example, suppose we wanted to design a hierarchy of shape classes for a computer graphics application class Shape { public: virtual void draw()=0; virtual void move(int x, int y)=0; virtual float area()=0; };

  40. 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

  41. class Square : public Shape { private: int x_pos, y_pos; int side; public: Square(int x, int y, int length) { x_pos=x; y_pos=y; side=length;} void draw() {// Call graphics commands …} void move(int x, int y) { x_pos+=x; y_pos+=y;} float area() { return side*side}; };

  42. class Circle : public Shape { private: int x_cent, y_cent; int radius; public: Circle(int x, int y, int r) { x_cent=x; y_cent=y; radius=r;} void draw() {// Call graphics commands …} void move(int x, int y) { x_cent+=x; y_cent+=y;} float area() { return 3.14159*radius*radius;} };

  43. The pure virtual functions in Shape are always overridden in the derived class through polymorphism void main() { Shape s; // Error! Can’t create a Shape object Shape* sp=new Square(5,6,20); // OK sp->move(10,10); // Calls Square::move() sp->draw(); // Calls Square::draw() float a=sp->area(); // Calls Square::area() };

  44. 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()

  45. 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

  46. 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()

  47. void main() { Shape** s = new Shape*[5]; s[0]=new Square(0,0,5); s[1]=new Circle(2,4,3); s[2]=new Circle(3,1,6); s[3]=new Square(2,5,3); s[4]=new Square(2,6,7); for (int j=0; j<5; j++) s[j]->draw(); // Draws each shape on the list };

  48. 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

  49. class Triangle : public Shape { private: int x_vertices[3], int y_vertices[3]; public: Triangle(int[] xv, int[] yv) {…} void draw() {…} void move(int x, int y) {…} float area() {..} }; void main() { … for (int j=0; j<5; j++) s[j]->draw(); // Draws each shape on the list }

  50. 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

More Related