Object-oriented programming

Introduction

Recall that in previous notes, we looked at designing programs using a data modeling approach. We saw that by thinking about the data in a problem description, we can design reusable modules that can be used in a variety of applications. This method can also be called object-oriented programming since we are orienting our design primarily on the objects (or data models) inherent in a problem description. The C++ programming language adds to the C language a number of object-oriented features that make it ideal for object-oriented design and programming. Some of these features include the following concepts (each of which we’ll be studying in-depth throughout the semester).

 

 

 

Classes and Objects

After identifying the data in a problem description, we saw how to define a data model using both classes and structures in C++. Defining a class in C++ allows us to create a new data type that encapsulates what the data model knows (the variables stored in the class) and what the object does (the member functions stored in the class). Below is an example of a C++ class definition that defines a new data type named Solution. This data type can be used to describe a solution that (as we saw earlier) can be used to solve problems associated with mixing different types of fluids.

 

class Solution

{

private:

  float vol;

  float per;

 

public:

  Solution() 

  {

    vol = 0.0;

    per = 0.0;

  }

  void Init(float v, float p);

  void Print();

  void Mix(Solution *, Solution *);

  void setVol( float v ) { vol = v; }

  void setPer( float p ) { per = p; }

  float getVol() { return vol; }

  float getPer() { return per; }

};


We can declare variables of this new data type in a program as in the example below.

 

main()

{

  Solution x;

  ...

}

 

When we declare variables of class definitions, we refer to them as objects. Note the difference between a class and an object. A class is the definition of an object, not the actual object itself. An object is a variable that has memory allocated to store the information defined in the class definition. We refer to this as the instantiation of the class. An object is an instantiation of a specific class data type. Think of the analogy of building a house. The blueprint defines what is needed to build the house and how the house is to be built. Nothing is actually built yet, rather just the definition of the house has been specified. The blueprint is equivalent to a class definition. No memory is allocated with a class definition (nothing is “built” yet). To build the house, construction workers look to the blueprint and use the specified material to build the house (lumber, nails, concrete). We could say that the actual house is the instantiation of the blueprint. Similarly, when a variable (or object) is declared, memory is allocated according to the class definition and ready for use within a program. The object is instantiated from the class definition. In our example above, we say that the object x is an instantiation of the Solution class.

 

As another example of a class, consider writing code to manage a window on a screen typical of any computer application. We identify a window as a primary data model. Our design begins by asking “what does a window know?” and “what does a window do?” Our initial design specification might look like the following. Such a table is a good method to use when designing classes.

 

Window

What does it know?

What does it do?

Position

Color

Border type

...

 

Moves horizontally

Moves vertically

...

A class definition is used to implement such a design by encapsulating what the window knows (under the keyword private) and what a window does (under the public keyword).

      class window
    {
    private:
        int xpos, ypos;       // position on screen
        int window_type;      // window design
        int border_type;      // border type
        int window_color;     // window color


    public:
        // Move horizontally

        move_hz( int amt );

   

        // Move vertically
        move_vt( int amt );
    };

 

Internal Data
Objects store, in memory, the actual values for each of the variables in a class definition. These values define the state of an object at any point in time during the execution of a program. We also refer to this information as internal data since the values of the variables are internal to an individual object. Consider the analogy of a person. At any given time, we all have information that is internal to us (blood pressure, weight, heart beat). At different points in time, these values may change as they define our state. Likewise, declared objects may take on different values defining their state. As such, we refer to these as state variables. In our example above, the state variables for a window object would include the position, window type, border type, and color.

 

Information Hiding
Depending on the class, state variables might contain some very important information (imagine a class storing information describing bank accounts!). Because of this importance, we must consider how to protect and hide information inside of an object. In object-oriented programming, it helps to consider two types of programmers, class developers and application developers.

Class Developer vs. Application Developer

A class developer is responsible for the design, creation, and protection of a class. This is the software developer that writes the actual class definition and member functions. After the class is written, it might be used within a program or application by either the same individual or by other programmers. For our discussion, let’s call these programmers application developers. These programmers use the class to declare objects to solve some problem in an application. They are only using the class, they are not writing the class. This is often the case in organizations with software development teams. One group of developers might be responsible for designing and writing reusable classes while other programmers use the classes to write applications. This is also the case with application programming interfaces (API) or class libraries. Oftentimes, some specific type of functionality is provided as a software package that can be used to create a variety of applications. A class developer might write, for example, several C++ classes to create and run a 3D game engine. These classes can be organized and stored in a library or software package that can be used by application developers to write specific types of 3D games. Finally, sometimes the class developer and the application developer might be the same person (as in the case for student programming assignments).

 

The distinction between class and application developer comes into play when considering the security of objects. Who should have access to the values in the internal data (or state variables) of objects? When designing classes, it is good practice to restrict access to an object’s state variables only to the member functions of the class itself. In other words, the member functions, designed and written by the class developer, should only be able to directly modify state variables. All other access to the values of state variables in applications is accomplished by calling member functions. In this way, information is said to be hidden (or protected) from the object users. C++ provides information hiding through the use of keywords private, public, protected. Consider the example below defining a class to describe a bank account. An initial design written by a class developer might look like the following:

 

class Account
{
public:
      int balance;            // stores the amount of money

 

      // Constructor   

      Account( int initial ) { balance = initial; }

 

      // Withdraw money   

      int withdraw( int amount )
      {

        if( balance >= amount )
          balance -= amount;
        else
          cerr << "Insufficient funds." << endl;
        return balance;
      }

     

      // Deposit money

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

 

An application developer using this class might write a program that looks like the following. Without protecting the value of the state variable, balance, any application developer could embezzle money using this unprotected class!

 

main()
{
      Account Sue( 30 );
      Sue.balance += 10000; // embezzle some money
}
 

To protect and hide the information from a user, a class developer might re-design the definition to look like the following. Note the addition of the private keyword to protect access (by users) to the balance state variable.

 

class Account
{
private:
      int balance;
public:
      Account(int initial) { balance = initial; }

      int withdraw(int amount) ...

      int deposit(int amount) ...
};

 

With this protection, the program above will not even compile and report the following error. In this way, the class developer has hidden information about the balance to users of the class.

error: main() cannot access Account::balance: private member

Access to the balance is now only provided through the withdraw and deposit member functions. If this is the case, would the following application be any different from the previous program?

 

main()
{
      Account Sue( 30 );
      Sue.deposit( 10000); // embezzle some money
}

The answer would be no! What might we add to our deposit function, then, to restrict such an illegal deposit to an account? We could add some type of security check within the function to make sure the person depositing is the owner of an account. We might add something like the following as the class developer. In this way not only can we create objects that are secure but we can hide information to users of the class.

public:
       

      // Deposit money

      int deposit( int amount )
      {
        // Check personal id number

        if( id number matches )

        {

            balance += amount;
            return balance;

        }

        else

        {

            // Call the police

        }
      }

Finally, a good design practice is to allow users of objects (application developers) access to state variables through what is referred to as access functions. These functions allow users to set (assign) and get (retrieve) values from state variables. The get and set functions in the Solution class above (repeated below) above are examples of such access member functions.

 

class Solution

{

private:

  float vol;

  float per;

 

public:

  Solution() 

  {

    vol = 0.0;

    per = 0.0;

  }

  void Init(float v, float p);

  void Print();

  void Mix(Solution *, Solution *);

  void setVol( float v ) { vol = v; }

  void setPer( float p ) { per = p; }

  float getVol() { return vol; }

  float getPer() { return per; }

};

 

 

 

 

Inheritance

After further analysis of our window design above, we might notice a few additional characteristics. For example, we might notice that a window can be a variety of different types. For example a window could be one of the following:

-         pop-up windows

-         menu windows

-         help message windows

-         dialog box windows

In addition, we might also notice that many of these different types of windows have many characteristics in common. For example, each would include

-         position

-         color

-         border

-         ability to move horizontally

-         ability to move vertically

 

C++ provides a feature that allows similar classes to be grouped and organized efficiently. Organized hierarchically, classes can inherit properties (variables and functions) from other classes. Inheritance allows classes to be built from other classes. In this way, classes can be built upon what is already in place (another advantage of re-usability). As we’ll see in depth later, inheritance provides the following properties:

-         define a basic class with common traits, then create slight derivations

-         each derivation created will automatically receive attributes of its parent

-         this basic class is termed a base class

-         the slight derivations from the base class are termed derived classes

-         derived classes are inherited from base classes

-         inheritance allows creation of a hierarchy of classes

-         each subclass inherits or shares properties with parent

-         derived classes can...

o       share data and code from base class

o       add its own special code and data to it

o       change those items that need to be different

 


  

Messages, Methods, and Behaviors
When member functions are called by objects in an application, we refer to this as message passing. Think of it as the method by which an application developer communicates with objects. A message, or command, sent to an object, telling the object to do something. For example, the program below is sending a message to a window object to “move itself horizontally.”

main()
{
      window w;

      w.move_hz( 10 );
}

The manner in which an object responds to a message is termed its behavior. Behaviors are implemented in member functions, or methods. Different objects can have different behaviors responding to the same message.

 

Implementation and Interface

Another feature of object-oriented programming is the ability to separate the implementation of an object from the interface of an object. Consider again the example below.

 

main()
{
      window w;

      w.move_hz( 10 );
}

 

Note the user of the object (application developer) does not tell the window object how to move. The object knows how to do that. The object is simply passed a message. We can say that a user communicates with an object through an interface of a set of provided messages. We are given, by the class developer, a variety of member functions that define how it will communicate. These member functions (methods) provide the interface to anyone using an object instantiated from the class. The user of an object does not need to know (or care) about how the message will be performed, only that it will be performed. How an object performs a task (the instructions in member functions) is referred to as the implementation. In this way, we say that the interface of an object is separated from the implementation of an object. The internal details of the implementation can and should be hidden from users of an object.

 

Why is separating the interface from the implementation so important? Consider the example above where the message, “move horizontally,” is repeated thousands of time in various files that form some large application using the window class. Now suppose the class developer has developed a new, more efficient way to move a window horizontally. The class developer can simply re-write the implementation (the member function move_hz) without ever requiring the application developers to modify the large program using this message. The message (or interface) never changes, only the implementation. The implementation is hidden and separated from the interface. Consider also proprietary classes that are written and sold as part of a software product. Users writing programs using such classes would only be given access to the list of messages provided by the class and NOT the actual implementation (code) of each of the messages. In this way, the separation allows the class to be used without giving access to the inner-workings of the code.