Supplemental Lecture from Mercer Object-Oriented Programming (OOP) With some Borland Windows examples LESSON 21 The Three Major Features of OOP 1. Encapsulation: Combining data with the operations that manipulate the data into a C++ class. Implementation details are hidden from users (information hiding). Classes give us data integrity. We can't accidentally mess up the object. 2. Inheritance: A relationship among classes where one class shares the data and operations of another class. Inheritance allows for a hierarchy of classes (classification). A derived class adds data and operations or alters the meaning of an operation defined in a parent class. 3. Polymorphism: Where an operator or function name means different things to different objects. (Note: 5 / 2 is a different operation than 5.0 / 2.0) Objects An object is an entity stored in memory that we can apply operations to. The terms object and variable are interchangeable. Every object has identity (a name), state (value(s) stored in memory), and operations (such as =, +, -, *, /, >>, <<, eof, withdraw, deposit, toUpper, getNameAndPIN, recordWithdrawal) Classes A class is data and the set of operations that manipulate that data. The terms class and data type are interchangeable. Classes/objects are the salient feature of object-oriented programming (OOP). An object is an instance of a class (a variable is one instance of a type) The C++ class is an excellent mechanism for implementing encapsulation. Also, the salient feature of object-oriented programming. Also, the major difference between C and C++. (C++ was first called C with Classes) We experienced all of OOP very quickly! 1. Encapsulation: C++ classes (float, ostream, istream) hide many details. 2. The ostream and istream classes are part of a class hierarchy. Ios ostream istream 3. Polymorphism in action: the << operator means different things for different classes of object. This operation cout << "Grade"; operates differently than this operation: cout << 0.25*test1 + 0.25*test2 + 0.50*finalExam; Inheritance Inheritance is a major feature of object-oriented programming that provides the ability to create new classes from existing ones. When designing object-oriented programs it is sometimes helpful to think in terms of IS-A relationships. For example, a savings account IS-A bank account that pays interest. Using the OOP technique of inheritance, it is easier to create other classes. With inheritance, a collection of related classes can be made into a hierarchy of classes that share common data and operations. To implement a bank account class that pays interest. we could start from scratch. Or we could copy the files containing the baseAccount (shown later) class declaration and implementation, and modify these two files accordingly. This would leave us with two separate modules, each implementing separate yet related object classes. Or perhaps we should analyze this situation before designing a class hierarchy. Let's classify the bank account objects that might exist in a real bank. How many different types (classes) of bank accounts exist at your local neighborhood bank? Multiple Choice _________ ? a. 5 b. 10 c. 15 d. more than 15 Imagine we were to analyze and design software to model these bank accounts We could classify them following an entomologists scientific model of classification: insects winged … wingless … Can we classify bank accounts in a similar hierarchical classification? We should have a base class that describes all the characteristics common to every bank account (the insect class describes characteristics that are common to ALL insects--winged and wingless) An insect has 6 legs, a thorax, and a body. If we know what an insect is, we have a good idea of what winged and wingless insects are. If we know what a basic bank account is, we have a good idea of other bank accounts. Let's figure out what a basic bank account should look like. What data should be in every bank account class? ___________ _____________ ___________ ___________ _____________ ___________ But then what is the first split on a hierarchy of bank account classes: Interest accounts and non-interest accounts, or personal account and business accounts, or something else entirely? Let's keep it simple and do the first split as checking and savings where checking accounts do not pay interest and savings accounts do. BaseAccount // a base class with no instances checking savings // two derived classes What operations (besides debit and credit) should be in every bank account class? ___________ _____________ __________ ___________ debit credit We obviously have to plan for inheritance. To derive classes, we supply the common member functions in a base class supply protected:data members class baseAccount { public: baseAccount::baseAccount(string initName,string initPIN,double initBalance); void baseAccount::debit(double amount, char transCode); // POST: Debit accountBalance by amount and record transaction- audit trail void baseAccount::credit(double amount, char transCode); // POST: Credit accountBalance by amount and record transaction- audit trail void baseAccount::deposit(double amount); // POST: Add amount to balance and record transaction for audit trail void baseAccount::withdraw(double amount); // POST: Subtracts amount from balance and records transaction- audit trail void baseAccount::applyInterest(); // POST: Does nothing, but may be overridden in descendant classes. double baseAccount::balance() const; // POST: Return accountBalance string baseAccount::name() const; // POST: Return accountName string baseAccount::PIN() const; // POST: Return accountNumber // PROTECTED MEMBERS ARE ACCESSABLE IN DERIVED CLASSES (private ones aren't) protected: // Anticipate classes derived from this class string accountName; string accountPIN; double accountBalance; }; Even when we implement member functions, we must consider the inheritance feature of OOP. This might mean defining member functions to do little or even nothing at all. The credit and debit functions are general enough to make adjustments to the accountBalance and at the same time doing whatever is necessary such as creating an audit trail (they display data to the screen at this time). void baseAccount::credit(double amount, char transCode) { accountBalance = accountBalance + amount; // TBA: Generate an audit trail. Now just output the transaction cout << accountName << ' ' << amount << ' ' << transCode << endl; } void baseAccount::debit(double amount, char transCode) { accountBalance = accountBalance - amount; // TBA: Generate an audit trail. Now just output the transaction cout << accountName << ' ' << amount << ' ' << transCode << endl; } The deposit and withdrawal operations are very minimal. This is what derived classes inherit when there is no new meaning given to deposit or withdraw. void baseAccount::deposit(double amount) { // Give this to any derived class that does not override deposit credit(amount, depositCode); // Assume const char depositCode = 'D'; } void baseAccount::withdraw(double amount) { // Give this to any derived class that does not override withdraw debit(amount, withdrawalCode); // Assume const char withdrawalCode = 'W'; } And nothing happens when applyInterest is called. This allows any derived class to give applyInterest new meaning or prevent incrementing the balance. void baseAccount::applyInterest() { // Do nothing in the base class } Derived Classes via Inheritance We derive class savings from class baseAccount like this while adding a new 4 parameter constructor, an interest operation, and a data member: class savings : public baseAccount { public: savings::savings(string initName, string initPIN, double initBalance, double initRate); void savings::applyInterest(); // OVERRIDE baseAccount::applyInterest // Post: accountBalance has been incremented by the interest. // The transaction has been recorded. void savings::withdraw(double amount); // OVERRIDE baseAccount::withdraw // PRE: amount <= accountBalance // POST: accountBalance = accountBalance - amount (if precondition is met) protected: // Anticipate classes derived from this class double annualInterestRate; }; The little colon ':' after the new class (and before public) can be read as inherits. The keyword public before the derived class name means that the derived class inherits all public and protected members of the parent class. The savings class inherits the member functions of its parent class (baseAccount) and adds a four argument constructor and applyInterest. Specifically, class savings inherits all three data members from baseAccount and adds the data member annualInterestRate. The following program shows the base class member functions are inherited by the derived class––a savings object has the same operations as baseAccount objects: name, PIN, and balance. Int main() { savings a("Hall", "1234", 100.00, 4.35); cout << a.name() << endl; cout << a.PIN() << endl; cout << a.balance() << endl; a.applyInterest(); cout << a.balance() << endl; return 0; } Output: HALL 1234 100 101.09 withdraw overrides (see below for defintion of override) the withdraw member function of the base class (baseAccount). applyInterest overrides the do-nothing applyInterest member of the base class. We are now compelled to give new meaning to both operations. We do this by defining the two member functions: savings::withdraw and savings::applyInterest since they were declared in the derived class. Overriding Methods Descendant classes inherit the member functions of their ancestors, unless the ancestor's methods are overridden. We override an ancestor's member functions by redeclaring the member functions in the derived class. This is another form of polymorphism––the ability to give one name to an action that operates on all the objects in the object hierarchy in a way that is appropriate to each object instance, even if the objects are constructed from different classes. Polymorphism is a powerful tool because it allows a class to be extended in ways that we may not anticipate. You don't need source code of base classes. Let's declare another derived class while overriding withdraw such that a loan is generated when amount > accountBalance (Note: we need to declare another three argument constructor) class checking : public baseAccount { public: checking::checking(string initName, string initPIN, double initBalance); void checking::withdraw(double amount); // OVERRIDES baseAccount::withdraw // POST: Generate a loan if necessary }; There are now three withdraw member functions which have different pre- and post-conditions. The previous two are reviewed here: void baseAccount::withdraw(double amount); // POST: Subtracts amount from balance and records transaction- audit trail void savings::withdraw(double amount); // OVERRIDE baseAccount::withdraw // PRE: amount <= accountBalance // POST: accountBalance = accountBalance - amount (if precondition is met) The next slide shows the implementation of all three withdraw functions (polymorphism): void baseAccount::withdraw(double amount) { // Give this to any derived class that does not override debit(amount, withdrawalCode); } void savings::withdraw(double amount) { // Output added for explanation's sake only if(amount <= accountBalance) debit(amount, withdrawalCode); } void checking::withdraw(double amount) { // Output added for explanation's sake only if(amount <= accountBalance) debit(amount, withdrawalCode); else { double loan = 0.0 ; // Make a loan in increments of loanIncrement (100.00 for example) while(loan + accountBalance < amount) loan = loan + loanIncrement; credit(loan, loanCode); debit(amount, withdrawalCode); } } Now for some examples using inheritance with Borland’s OWL Object Windows Library. The following code is taken from the owl/tutorial directory that comes with Borland. Each example derives a TDrawApp class from the Tapplication class provided by Borland to handle global variables and the main function among other things. Each OWL program must have an OwlMain function rather than a main function. //------------------------------------------------------- // ObjectWindows - (C) Copyright 1991, 1994 by Borland International // Tutorial application -- step01.cpp //-------------------------------------------------- #include #include #include class TDrawApp : public TApplication { public: TDrawApp() : TApplication() {} void InitMainWindow() { SetMainWindow(new TFrameWindow(0, "Sample ObjectWindows Program")); } }; int OwlMain(int /*argc*/, char* /*argv*/ []) { return TDrawApp().Run(); } Now add left and right mouse event handlers. //------------------------------------------------- // ObjectWindows - (C) Copyright 1991, 1994 by Borland International // Tutorial application -- step02.cpp //----------------------------------------------- #include #include #include class TDrawWindow : public TWindow { public: TDrawWindow(TWindow* parent = 0); protected: // Override member function of TWindow bool CanClose(); // Message response functions void EvLButtonDown(uint, TPoint&); void EvRButtonDown(uint, TPoint&); DECLARE_RESPONSE_TABLE(TDrawWindow); }; DEFINE_RESPONSE_TABLE1(TDrawWindow, TWindow) EV_WM_LBUTTONDOWN, EV_WM_RBUTTONDOWN, END_RESPONSE_TABLE; TDrawWindow::TDrawWindow(TWindow* parent) { Init(parent, 0, 0); } bool TDrawWindow::CanClose() { return MessageBox("Do you want to save?", "Drawing has changed", MB_YESNO | MB_ICONQUESTION) == IDNO; } void TDrawWindow::EvLButtonDown(uint, TPoint&) { MessageBox("You have pressed the left mouse button", "Message Dispatched", MB_OK); } void TDrawWindow::EvRButtonDown(uint, TPoint&) { MessageBox("You have pressed the right mouse button", "Message Dispatched", MB_OK); } class TDrawApp : public TApplication { public: TDrawApp() : TApplication() {} void InitMainWindow() { SetMainWindow(new TFrameWindow(0, "Sample ObjectWindows Program", new TDrawWindow)); } }; int OwlMain(int /*argc*/, char* /*argv*/ []) { return TDrawApp().Run(); } Now add x, y coordinates in the event handlers. //---------------------------------------------------------------- ------------ // ObjectWindows - (C) Copyright 1991, 1994 by Borland International // Tutorial application -- step03.cpp //---------------------------------------------------------------- ------------ #include #include #include #include #include class TDrawWindow : public TWindow { public: TDrawWindow(TWindow* parent = 0); protected: // Override member function of TWindow bool CanClose(); // Message response functions void EvLButtonDown(uint, TPoint&); void EvRButtonDown(uint, TPoint&); DECLARE_RESPONSE_TABLE(TDrawWindow); }; DEFINE_RESPONSE_TABLE1(TDrawWindow, TWindow) EV_WM_LBUTTONDOWN, EV_WM_RBUTTONDOWN, END_RESPONSE_TABLE; TDrawWindow::TDrawWindow(TWindow* parent) { Init(parent, 0, 0); } bool TDrawWindow::CanClose() { return MessageBox("Do you want to save?", "Drawing has changed", MB_YESNO | MB_ICONQUESTION) == IDNO; } void TDrawWindow::EvLButtonDown(uint, TPoint& point) { char s[16]; TClientDC dc(*this); wsprintf(s, "(%d,%d)", point.x, point.y); dc.TextOut(point, s, strlen(s)); } void TDrawWindow::EvRButtonDown(uint, TPoint&) { Invalidate(); } class TDrawApp : public TApplication { public: TDrawApp() : TApplication() {} void InitMainWindow() { SetMainWindow(new TFrameWindow(0, "Sample ObjectWindows Program", new TDrawWindow)); } }; int OwlMain(int /*argc*/, char* /*argv*/ []) { return TDrawApp().Run(); } Use the x,y values from the mouse to draw //---------------------------------------------------------------- ------------ // ObjectWindows - (C) Copyright 1991, 1994 by Borland International // Tutorial application -- step04.cpp //---------------------------------------------------------------- ------------ #include #include #include #include class TDrawWindow : public TWindow { public: TDrawWindow(TWindow* parent = 0); ~TDrawWindow() { delete DragDC; } protected: TDC* DragDC; // Override member function of TWindow bool CanClose(); // Message response functions void EvLButtonDown(uint, TPoint&); void EvRButtonDown(uint, TPoint&); void EvMouseMove(uint, TPoint&); void EvLButtonUp(uint, TPoint&); DECLARE_RESPONSE_TABLE(TDrawWindow); }; DEFINE_RESPONSE_TABLE1(TDrawWindow, TWindow) EV_WM_LBUTTONDOWN, EV_WM_RBUTTONDOWN, EV_WM_MOUSEMOVE, EV_WM_LBUTTONUP, END_RESPONSE_TABLE; TDrawWindow::TDrawWindow(TWindow* parent) { Init(parent, 0, 0); DragDC = 0; } bool TDrawWindow::CanClose() { return MessageBox("Do you want to save?", "Drawing has changed", MB_YESNO | MB_ICONQUESTION) == IDNO; } void TDrawWindow::EvLButtonDown(uint, TPoint& point) { Invalidate(); if (!DragDC) { SetCapture(); DragDC = new TClientDC(*this); DragDC->MoveTo(point); } } void TDrawWindow::EvRButtonDown(uint, TPoint&) { Invalidate(); } void TDrawWindow::EvMouseMove(uint, TPoint& point) { if (DragDC) DragDC->LineTo(point); } void TDrawWindow::EvLButtonUp(uint, TPoint&) { if (DragDC) { ReleaseCapture(); delete DragDC; DragDC = 0; } } class TDrawApp : public TApplication { public: TDrawApp() : TApplication() {} void InitMainWindow() { SetMainWindow(new TFrameWindow(0, "Drawing Pad", new TDrawWindow)); } }; int OwlMain(int /*argc*/, char* /*argv*/ []) { return TDrawApp().Run(); } Use an input dialog box to get line thickness on right click. //---------------------------------------------------------------- ------------ // ObjectWindows - (C) Copyright 1991, 1994 by Borland International // Tutorial application -- step05.cpp //---------------------------------------------------------------- ------------ #include #include #include #include #include #include class TDrawWindow : public TWindow { public: TDrawWindow(TWindow* parent = 0); ~TDrawWindow() { delete DragDC; delete Pen; } void SetPenSize(int newSize); protected: TDC* DragDC; int PenSize; TPen* Pen; // Override member function of TWindow bool CanClose(); // Message response functions void EvLButtonDown(uint, TPoint&); void EvRButtonDown(uint, TPoint&); void EvMouseMove(uint, TPoint&); void EvLButtonUp(uint, TPoint&); DECLARE_RESPONSE_TABLE(TDrawWindow); }; DEFINE_RESPONSE_TABLE1(TDrawWindow, TWindow) EV_WM_LBUTTONDOWN, EV_WM_RBUTTONDOWN, EV_WM_MOUSEMOVE, EV_WM_LBUTTONUP, END_RESPONSE_TABLE; TDrawWindow::TDrawWindow(TWindow* parent) { Init(parent, 0, 0); DragDC = 0; PenSize = 1; Pen = new TPen(TColor::Black, PenSize); } bool TDrawWindow::CanClose() { return MessageBox("Do you want to save?", "Drawing has changed", MB_YESNO | MB_ICONQUESTION) == IDNO; } void TDrawWindow::EvLButtonDown(uint, TPoint& point) { Invalidate(); if (!DragDC) { SetCapture(); DragDC = new TClientDC(*this); DragDC->SelectObject(*Pen); DragDC->MoveTo(point); } } void TDrawWindow::EvRButtonDown(uint, TPoint&) { char inputText[6]; wsprintf(inputText, "%d", PenSize); if ((TInputDialog(this, "Line Thickness", "Input a new thickness:", inputText, sizeof(inputText))).Execute() == IDOK) { int newPenSize = atoi(inputText); if (newPenSize < 0) newPenSize = 1; SetPenSize(newPenSize); } } void TDrawWindow::EvMouseMove(uint, TPoint& point) { if (DragDC) DragDC->LineTo(point); } void TDrawWindow::EvLButtonUp(uint, TPoint&) { if (DragDC) { ReleaseCapture(); delete DragDC; DragDC = 0; } } void TDrawWindow::SetPenSize(int newSize) { delete Pen; PenSize = newSize; Pen = new TPen(TColor::Black, PenSize); } class TDrawApp : public TApplication { public: TDrawApp() : TApplication() {} void InitMainWindow() { SetMainWindow(new TFrameWindow(0, "Drawing Pad", new TDrawWindow)); } }; int OwlMain(int /*argc*/, char* /*argv*/ []) { return TDrawApp().Run(); } And finally add pull down menus and drawing a saved object in the paint routine to restore a window damaged on resize or uncovered from another window or after iconify. //---------------------------------------------------------------- ------------ // ObjectWindows - (C) Copyright 1991, 1994 by Borland International // Tutorial application -- step06.cpp //---------------------------------------------------------------- ------------ #include #include #include #include #include #include #include #include "step06.rc" typedef TArray TPoints; typedef TArrayIterator TPointsIterator; class TDrawWindow : public TWindow { public: TDrawWindow(TWindow* parent = 0); ~TDrawWindow() { delete DragDC; delete Pen; delete Line; } void SetPenSize(int newSize); protected: TDC* DragDC; int PenSize; TPen* Pen; TPoints* Line; // To store points in line. // Override member function of TWindow bool CanClose(); // Message response functions void EvLButtonDown(uint, TPoint&); void EvRButtonDown(uint, TPoint&); void EvMouseMove(uint, TPoint&); void EvLButtonUp(uint, TPoint&); void Paint(TDC&, bool, TRect&); void CmFileNew(); void CmFileOpen(); void CmFileSave(); void CmFileSaveAs(); void CmAbout(); DECLARE_RESPONSE_TABLE(TDrawWindow); }; DEFINE_RESPONSE_TABLE1(TDrawWindow, TWindow) EV_WM_LBUTTONDOWN, EV_WM_RBUTTONDOWN, EV_WM_MOUSEMOVE, EV_WM_LBUTTONUP, EV_COMMAND(CM_FILENEW, CmFileNew), EV_COMMAND(CM_FILEOPEN, CmFileOpen), EV_COMMAND(CM_FILESAVE, CmFileSave), EV_COMMAND(CM_FILESAVEAS, CmFileSaveAs), EV_COMMAND(CM_ABOUT, CmAbout), END_RESPONSE_TABLE; TDrawWindow::TDrawWindow(TWindow* parent) { Init(parent, 0, 0); DragDC = 0; PenSize = 1; Pen = new TPen(TColor::Black, PenSize); Line = new TPoints(10, 0, 10); } bool TDrawWindow::CanClose() { return MessageBox("Do you want to save?", "Drawing has changed", MB_YESNO | MB_ICONQUESTION) == IDNO; } void TDrawWindow::EvLButtonDown(uint, TPoint& point) { Line->Flush(); Invalidate(); if (!DragDC) { SetCapture(); DragDC = new TClientDC(*this); DragDC->SelectObject(*Pen); DragDC->MoveTo(point); Line->Add(point); } } void TDrawWindow::EvRButtonDown(uint, TPoint&) { char inputText[6]; wsprintf(inputText, "%d", PenSize); if ((TInputDialog(this, "Line Thickness", "Input a new thickness:", inputText, sizeof(inputText))).Execute() == IDOK) { int newPenSize = atoi(inputText); if (newPenSize < 0) newPenSize = 1; SetPenSize(newPenSize); } } void TDrawWindow::EvMouseMove(uint, TPoint& point) { if (DragDC) { DragDC->LineTo(point); Line->Add(point); } } void TDrawWindow::EvLButtonUp(uint, TPoint&) { if (DragDC) { ReleaseCapture(); delete DragDC; DragDC = 0; } } void TDrawWindow::SetPenSize(int newSize) { delete Pen; PenSize = newSize; Pen = new TPen(TColor::Black, PenSize); } void TDrawWindow::Paint(TDC& dc, bool, TRect&) { bool first = true; TPointsIterator i(*Line); dc.SelectObject(*Pen); while (i) { TPoint p = i++; if (!first) dc.LineTo(p); else { dc.MoveTo(p); first = false; } } } void TDrawWindow::CmFileNew() { Line->Flush(); Invalidate(); } void TDrawWindow::CmFileOpen() { MessageBox("Feature not implemented", "File Open", MB_OK); } void TDrawWindow::CmFileSave() { MessageBox("Feature not implemented", "File Save", MB_OK); } void TDrawWindow::CmFileSaveAs() { MessageBox("Feature not implemented", "File Save As", MB_OK); } void TDrawWindow::CmAbout() { MessageBox("Feature not implemented", "About Drawing Pad", MB_OK); } class TDrawApp : public TApplication { public: TDrawApp() : TApplication() {} void InitMainWindow() { SetMainWindow(new TFrameWindow(0, "Drawing Pad", new TDrawWindow)); GetMainWindow()->AssignMenu("COMMANDS"); } }; int OwlMain(int /*argc*/, char* /*argv*/ []) { return TDrawApp().Run(); } I hope this gives you some idea how one reuses code with inheritance and how close you are to being able to write windows code with what you have learned in CSCI 15A. Enjoy building your own programs and good luck to you. Jim [27]