10: Object-Oriented Programming

What We Will Cover


Continuations

Homework Questions?

Questions from last class?

An instance variable is hidden (shadowed) in a function when

  1. The instance variable has the same name as the function.
  2. The instance variable has the same name as a local variable in the function.
  3. The instance variable has the same name as the class.
  4. The instance variable has the same name as the file.

10.1: Working With Multiple Objects

Objectives

At the end of the lesson the student will be able to:

  • Code classes that use other objects
  • Describe how to pass objects to methods
  • Code arrays of objects

10.1.1: Objects as Instance Variables

  • Object-oriented programs use one or more objects to perform their tasks
  • Objects can be part of the data for other classes
  • private:
        Product product;
    
  • Different applications can use the same classes to create objects

For Example

  • Following example uses the Product class
  • Note how an object of the Product class is stored as part of the ProductOrder class
  • Also note how the Product class is initialized
  • When using another class, you do not need to know the inner workings of the other class
  • You only need to know the publicly available functions

#include <iostream>
using namespace std;

#include "Product.cpp"

class ProductOrder {
public:
    ProductOrder(string name, double price,
                 int prodQuantity);
    Product getProduct() { return prod; }
    double getQuantity() { return quantity; }
    double getTotal();
    void showData();
private:
    Product prod;
    int quantity;
};

ProductOrder::ProductOrder(string name, double price,
        int prodQuantity): prod(name, price) {
    quantity = prodQuantity;
}

double ProductOrder::getTotal() {
    return quantity * prod.getPrice();
}

void ProductOrder::showData() {
    cout << "Name: " << prod.getName()
         << "\nPrice: "
         << prod.getPrice()
         << "\nQuantity: " << quantity
         << "\nTotal Amount: "
         << getTotal() << "\n";
}

  • Note that the getTotal() function does not simply return a value
  • Instead, it calculates the total by using data stored in the class
  • Uses the getPrice() function to extract data from the Product class

10.1.2: Another Example Application

  • productorderapp.cpp is another driver application
  • Has a main function that starts an application

#include <iostream>
using namespace std;

#include "ProductOrder.cpp"

// For testing class Product
int main() {
    char choice = 'Y';
    string name;
    double price;
    int qty;
    while ('Y' == choice || 'y' == choice) {
        cout << "Enter a product name: ";
        cin >> name;
        cout << "Enter the price for a "
             << name << ": ";
        cin >> price;
        cout << "Enter a quantity of "
             << name << ": ";
        cin >> qty;

        ProductOrder po(name, price, qty);
        cout << "\nYou entered:\n";
        po.showData();

        cout << "\nEnter another product order? ";
        cin >> choice;
    }

    return 0;
}

  • Note that we have two applications sharing the same class Product
  • This is an example of code reuse

10.1.3: Passing Objects to Functions

  • Objects can be passed to functions as arguments
  • Usually objects are passed by reference
  • bool Product::isEqual(Product& p) {
        bool equal = false;
        if (p.name == name && p.price == price ) {
            equal = true;
        }
        return equal;
    }
    
  • We can test this function:
  • Product milk("Milk", 3.95);
    Product bread("Bread", 1.99);
    cout << "Testing product equality: "
         << milk.isEqual(bread) << endl;
    

10.1.4: Accessing Private Variables

  • One important note about private variables
  • Any object of a particular class can access the private variables of another object of that class
  • bool Product::isEqual(Product& p) {
        bool equal = false;
        if (p.name == name && p.price == price ) {
            equal = true;
        }
        return equal;
    }
    
  • Note that p.name accesses the private variable of the p object

10.1.5: Returning Objects from Functions

  • Class types can be returned from functions
  • Product makeProduct(string name, double price) {
        Product newProd(name, price);
        return newProd;
    }
    
  • In main we use the function:
  • Product myProd = makeProduct("Cheese", 3.45);
    cout << myProd.getName() << endl;
    
  • Usually more efficient to return by reference
  • Will learn how when covering pointers

10.1.6: Arrays of Objects

  • Any type can be made into an array, including class types
  • Product prods[10];
  • In the following, array elements are objects
  • You can call methods of the objects stored in each array element
  • #include <iostream>
    #include "product.cpp"
    using namespace std;
    
    const int NUM_PRODS = 3;
    
    int main(void) {
        Product store[] = {
            Product("Milk", 3.95),
            Product("Bread", 1.09),
            Product("Cheese", 4.59)
        };
        for (int num = 0; num < NUM_PRODS; num++) {
            Product prod = store[num];
            cout << prod.getName() << " @ ";
            cout << store[num].getPrice();
            cout  << endl;
        }
        return 0;
    }
    
    

Notes on the Code

  • Code that accesses each individual array element
  • Product prod = store[num];
    
  • Code that calls a function on each array element
  • cout << store[num].getPrice();
    

10.1.7: Summary

  • Object-oriented programs use one or more objects to perform their tasks
  • Objects can be part of the data for other classes
  • private:
        Product product;
    
  • Different applications can use the same classes to create objects
  • A class may use another class as a type for a member variable
  • Classes can be function parameters
    • Objects of the class type are passed as arguments
    • Usually passed by reference for efficiency
  • Objects can be returned from functions as well

Exercise 10.1

  1. Start a text file named exercise10.txt.
  2. Prepare the exercise header as described in the HowTo on submitting exercises
  3. Label this exercise: Exercise 10.1
  4. Submit all exercises for today's lesson in one file unless instructed otherwise
  5. Complete the following and record the answers to any questions in exercise10.txt.

Specifications

  1. Trace the ProductOrderApp program by listing the object names and method calls in the order of their occurrence:
  2. objName.methodName()

    If an object is not instantiated, use the class name instead. For example, the first method few method calls are:

    main()
    po.ProductOrder()
    
  3. Record your sequential listing in your exercise10.txt file.
  4. In addition, record answers to the following questions:

Q1: In ProductOrder, what is the number of the line that instantiates the Product object?

Q2: In productorderapp.cpp, what is the number of the line that calls the constructor of ProductOrder?

Q3: How many objects are created when the following line of code is executed?

ProductOrder po(name, price, qty);

10.2: ADTs and Separate Compilation

Objectives

At the end of the lesson the student will be able to:

  • Describe how to make a class an abstract data type
  • Describe how to separate the interface from the implementation
  • Use separate compilation

10.2.1: Designing Classes as ADTs

  • Data types, such as an int, has certain values that can be stored
  • To be useful, you must be able to operate on these values
  • How the operation occurs, we do not need to know to use them
  • We want to keep these observation in mind when we design classes
  • Ideally, we want to design our classes as abstract data types (ADTs)
  • We keep details of the operation from the programmers using the class
  • Our public interface (functions) are all another programmer needs to know to use our class
  • Designing in this way provides for more modularity and easier changes

Ensuring Classes are ADT's

  • The key to designing a good class is to separate the implementation from the interface
  • How to use the class is presented to the programmer in detail
  • How the class is implemented is hidden
  • We start the process by making all member variables private members
  • We make public the member functions a programmer needs to use the class
  • We then fully specify, using block comments, how to use each public function
  • In addition, we usually make helper functions private members
  • Our next step is to hide the actual code from the public function declarations (prototypes)
  • C++ allows you to place the interface and the implementation in separate files
    • Each part can be compiled separately
    • Known as separate compilation
  • In C++, this provides the best way to ensure a class is an ADT
  • However, the declarations of private members are still exposed
  • For a simple program with one class and a driver, you end up with 3 files

10.2.2: Example of Separate Compilation

  • Separate compilation starts by placing class declaration in a separate file
  • For example, we can save our Product class declaration in a separate file
  • #ifndef PRODUCT_H
    #define PRODUCT_H
    
    #include <string>
    using namespace std;
    
    class Product {
    public:
        // Constructors
        Product();
        Product(string newName, double newPrice);
        // Instance functions
        string getName() { return name; }
        double getPrice() { return price; }
        void setName(string newName);
        void setPrice(double newPrice);
    private:
        // Instance variables
        string name;
        double price;
    };
    
    #endif
    
  • We name the file: product.h
  • The remained of the class definition stays in the product.cpp file
  • However, the product.cpp definitions still need the declarations
  • Thus, we include the product.h file in product.cpp
  • #include "product.h"
    
    // no-arg constructor
    Product::Product() {
        name = "Unknown";
        price = 0.0;
    }
    
    Product::Product(string newName, double newPrice) {
        setName(newName);
        setPrice(newPrice);
    }
    
    void Product::setName(string newName) {
        if (newName.length() == 0) {
            name = "Unknown";
        } else {
            name = newName;
        }
    }
    
    void Product::setPrice(double newPrice) {
        if (newPrice > 0.0) {
            price = newPrice;
        } else {
            price = 0.0;
        }
    }
    
    
  • The main() function is the starting point of any application
  • Most applications use more than one object
  • Thus, you usually place main() in a separate file
  • We name the file depending on the application
  • In this case, productapp.cpp seems appropriate
  • #include <iostream>
    using namespace std;
    
    #include "product.h"
    
    // For testing class Product
    int main() {
        char choice = 'Y';
        string name;
        double price;
        while ('Y' == choice || 'y' == choice) {
            cout << "Enter a product name: ";
            cin >> name;
            cout << "Enter the price for a "
                 << name << ": ";
            cin >> price;
    
            Product prod(name, price);
            cout << "You entered:"
                 << "\n   Name: " << prod.getName()
                 << "\n   Price: " << prod.getPrice()
                 << endl;
    
            cout << "Enter another product? ";
            cin >> choice;
        }
    
        return 0;
    }
    
  • To use the Product class, all we need to access is the class declaration
  • We gain access to the declaration by including the product.h file

Compiling

  • Because we have multiple .cpp files, compiling is a two-step process:
    1. Compile the all the .cpp files into object files
    2. Link all the objects files together into an executable file
  • We compile the class and the application into object files:
  • g++ -c product.cpp
    g++ -c productapp.cpp
    
  • Then we link both files together into the application
  • g++ -o productapp productapp.o product.o
    
  • We then run the application in the usual way:
  • productapp
    

10.2.3: Instructions for Separate Compilation

  1. Separate the interface from the implementation
    1. Place the class declaration into a classname.h file
    2. Place #ifndef...#enddef around the definition in the classname.h file
    3. Please the class implementation into a classname.cpp file
    4. Code a #include "classname.h" directive in the classname.cpp file
    5. Usually placed after the core library includes
  2. Place the main function in a separate file
    1. Place the application main function into an appname.cpp file
    2. Code a #include "classname.h" directive in the appname.cpp file
  3. Compile the class and the driver into object files
  4. g++ -c classname.cpp
    g++ -c appname.cpp
    
  5. Link both files together into the application
  6. g++ -o appname appname.o classname.o
    

10.2.4: Makefiles

  • It quickly becomes tedious to recompile code with multiple source files
  • You can use a program named make to automatically recompile your files
  • However, you must create a file named Makefile with instructions for the make program

For Example

    # simple makefile
    
    # define target dependencies and files
    productapp: productapp.o product.o
    	g++ -o productapp productapp.o product.o
    
    # define how each object file is to be built
    productapp.o: productapp.cpp product.h
    	g++ -c productapp.cpp -W -Wall --pedantic
    
    product.o: product.cpp product.h
    	g++ -c product.cpp -W -Wall --pedantic
    
    # clean up
    clean:
    	rm -f productapp.exe *.o
    
    
  • Note that the large blank areas before a command is a tab character
  • To use a Makefile, you type make at the command line
  • Most people write a Makefile by modifying an existing one

Important Information

  • Lines starting with a hash mark (#) are comments and are ignored
  • Rules define which files depend on others and take the form:
  • targetfile : sourcefiles
    <tab>commands you normally type
    
  • Note the tab character, which is required prior to defining the commands

Further Information

10.2.5: Summary

  • All classes should be coded as ADTs
  • C++ allows you to place the interface and the implementation in separate files
  • Separate compilation helps to ensure your classes are ADTs
  • You can write a Makefile for easy recompiling

Exercise 10.2

  1. Label this exercise: Exercise 10.2
  2. Submit all exercises for today's lesson in one file unless instructed otherwise
  3. Complete the following and record the answers to any questions in exercise10.txt.

Specifications

  1. Apply the separate compilation process to the MyRectangle class shown below and compile the files.
  2. Q1: What is the name of the file that contains the class declaration?

    Q2: What is the name of the file that contains the main() function?

  3. Submit your updated MyRectangle class files along with exercise10.txt.
#include <iostream>
using namespace std;

class MyRectangle {
public:
    MyRectangle(double length, double width);
    void showData();

private:
    double length;
    double width;

};

MyRectangle::MyRectangle(double newLength, double newWidth) {
    length = newLength;
    width = newWidth;
}

void MyRectangle::showData() {
    cout << "length: " << length
         << "\nwidth: " << width
         << endl;
}

// For testing
int main() {
    MyRectangle rec(3.0, 5.0);
    rec.showData();

    return 0;
}

10.3: Developing Classes

Objectives

At the end of the lesson the student will be able to:

  • Describe the software development process
  • Write function stubs
  • Develop drivers for testing classes

10.3.1: About Software Development

  • Roughly four main steps to developing software:
    1. Analysis: explore what is wanted and develop an initial plans
    2. Design: class design and algorithm development
    3. Implementation: coding and testing classes
    4. Deployment: final documentation and submitting the code
  • Software development is an iterative process
  • Often need to go back and repeat steps as you discover new information
  • In this section, we discuss the design and implementation steps

Further Information

10.3.2: Designing Classes

  • The organization of classes used in a program is known as its architecture
  • Classes are often organized into three main categories
    1. User interface classes
    2. Problem-domain classes
    3. Database classes
  • This is known as a three-tier architecture
  • Most of the work in this course is centers around the problem-domain classes
  • User interfaces are usually graphical, which is beyond the scope of this course
  • Database classes are beyond the scope of this course as well

Designing Problem-Domain Classes

  • Identify the classes needed for the program
    • Often spelled out in the specifications
    • If not, then read the specification looking for nouns
    • For example: customers, addresses, products, sales
    • Nouns become the classes of the program
  • Identify the functions needed for each class
    • Often spelled out in the specifications
    • If not, read the specifications and identify the actions that need to occur
    • Usually identified by the verbs in the specification
    • These actions become the functions for the class
  • At his point, you are ready to implement the first iteration of your classes

10.3.3: First Step

  • As a first step, write a class declaration with function and variable declarations
  • For example:
  • #include <iostream>
    using namespace std;
    
    class MyClass {
        public:
            MyClass();
            int getValue() { return value; }
            int setValue(int newValue) { value = newValue; }
            bool isCorrect();
            void performCalculation();
        private:
            int value;
    };
    
    int main(void) {
        MyClass mc;
    }
    
  • For get and set functions, just code a simple function definition in the class declaration
  • int getValue() { return value; }
    int setValue(int newValue) { value = newValue; }
    
  • You can compile a class declaration to check for errors
  • $ g++ myclass.cpp -c

10.3.4: Initial Implementation

  • When you first implement functions, it often makes sense to use stubs
  • For the constructors, just assign default values
  • MyClass::MyClass() {
        value = 0;
    }
    
  • If a function does not have to return anything, you can code an empty function
  • void MyClass::performCalcuation() { }
  • If you want to see when a function is called, include a print statement
  • void MyClass::performCalcuation() {
        cout << "Executing performCalcuation\n";
    }
    
  • If the function needs to return a value, just return a known value
  • bool MyClass::isCorrect() {
        return true;
    }
    
  • You can return later and write the complete function at a later time
  • Keep stubs so simple that you are confident they perform correctly
  • If stubs get too complex, it is better to just write the function
  • You can compile the first implementation to check for errors
  • $ g++ myclass.cpp -c
  • Once the class is "stubbed out" you should develop a driver for testing

10.3.5: Drivers and Testing

  • Driver programs allow testing of each function
  • Once a function is tested, it can be used in the driver program to test other functions
  • Drivers allow you to complete each function of your class one at a time
  • You can start with drivers in the same class
  • Later move the driver into a separate file so you can use the class in your main application

For Example

  • Following is an example of a main function to drive the tests for the stubs shown above
  • You add to the tests as you develop your class
  • If you make changes, you can rerun the tests to verify no new errors were introduced
int main() {
    cout << "Calling the constructor\n";
    MyClass mc;

    cout << "Calling the get and set functions\n";
    mc.setValue(2);
    if (mc.getValue() != 2) cout << "Error\n";

    cout << "Calling performCalcuation\n";
    mc.performCalculation();
    cout << "Verifying the results\n";
    if (!mc.isCorrect()) cout << "Error\n";

    return 0;
}

10.3.6: Testing and Iterating

  • We now have a complete first iteration
  • #include <iostream>
    using namespace std;
    
    class MyClass {
        public:
            MyClass();
            int getValue() { return value; }
            int setValue(int newValue) { value = newValue; }
            bool isCorrect();
            void performCalculation();
        private:
            int value;
    };
    
    MyClass::MyClass() {
        value = 0;
    }
    
    bool MyClass::isCorrect() {
        return true;
    }
    
    void MyClass::performCalculation() {
        cout << "Executing performCalcuation\n";
    }
    
    int main(void) {
        cout << "Calling the constructor\n";
        MyClass mc;
    
        cout << "Calling the get and set functions\n";
        mc.setValue(2);
        if (mc.getValue() != 2) cout << "Error\n";
    
        cout << "Calling performCalcuation\n";
        mc.performCalculation();
        cout << "Verifying the results\n";
        if (!mc.isCorrect()) cout << "Error\n";
    
        return 0;
    }
    
  • Now we can compile and link the code to check for errors
  • $ g++ myclass.cpp
  • Then run the tests
  • $ ./a
    Calling the constructor
    Calling the get and set functions
    Calling performCalcuation
    Executing performCalcuation
    Verifying the results
    

Subsequent Iterations

  • Now implement each function, or part of a function, and retest
  • Make whatever changes are needed as your design evolves
  • Modify your tests to reflect your design
  • When you start using multiple classes, move to separate compilation

10.3.7: Summary

  • There are roughly four main steps to developing software:
    1. Analysis: explore what is wanted and develop an initial plans
    2. Design: class design and algorithm development
    3. Implementation: coding and testing classes
    4. Deployment: final documentation and submitting the code
  • Software development is an iterative process
  • You often need to go back and repeat steps as you discover new information
  • Once you identify your classes and functions, you are ready to code your first iteration
  • Write a class declaration with function and variable declarations
  • When you first implement functions, it often makes sense to use stubs
  • You can return later and write the complete function at a later time
  • Once the class is "stubbed out" you should develop a driver for testing
  • Keep the tests up to date as you change the program

Exercise 10.3

  1. Label this exercise: Exercise 10.3
  2. Submit all exercises for today's lesson in one file unless instructed otherwise
  3. Complete the following and record the answers to any questions in exercise10.txt.

Specifications

  1. Spend 10 minutes reviewing the specification for A10: Storing Objects.
  2. We will review the specification and design the classes during this exercise.

  3. Record a description of the design in your exercise10.txt file.
  4. After our design discussion, and as time permits, develop a first implementation of your new classes.

Wrap Up

Home | WebCT | Announcements | Day Schedule | Eve Schedule
Course info | Help | FAQ's | HowTo's | Links

Last Updated: November 16 2004 @19:10:00