Week 12

Classification

Classification is a mechanism which we use to understand the natural world around us. As infants we begin to recognize different categories, like food, toys, pets, and people. As we mature, we divide these general classes into subcategories like siblings and parents, vegetables and dessert.

A little girl looking at a bird in a nest.

When faced with a new object, we understand it by fitting it into the categories with which we’re acquainted:

  • Does it taste good? Perhaps it’s dessert.
  • Is it soft and fuzzy? Maybe it’s a pet.
  • Otherwise, it’s most certainly a toy of some sort!

Encapsulation—the specification of attributes and behavior as a single entity—allows us to build on this understanding of the natural world as we create software. By creating classes and objects that model categories in the "real world," we have confidence that our software solutions closely track the problems we are trying to solve.

Once we've designed our own classes, instead of using computer files and variables, our programs can be expressed in terms of Customers, Invoices, and Products.

Week 12

Inheritance

A picture of pie and broccoli.

Inheritance adds to encapsulation the ability to express relationships between classes. Think back to the categories, "desserts" and "vegetables." Cherry pie and broccoli are both, arguably, edible items; for humans, they belong to the foodclass.

Yet, in addition to belonging to the food class, cherry pie is a kind of dessert, but broccoli is a kind of vegetable. Both desert and vegetable represent subcategories of foods. Both cherry pie and broccoli are kinds of food, but, thankfully, the food class itself consists of more than just these two items. Cherry pie and broccoli are just two small subsets of all possible food types.

Thus, the relationship between food and cherry pie class is one of superset (food) and subset (cherry pie). In classical object-oriented terms, we call this the superclass-subclass relationship. C++, which has its own terminology, calls it the base class-derived class relationship.

The food pyramid.

Base and derived classes are arranged in a hierarchy, with one base class divided into numerous derived classes, and each derived class divided into more specialized kinds of derived classes. That’s what we find with the food class.

It can be divided into desserts, vegetables, soups, salads, and entrees. Each category can be further divided into more specialized kinds of food.

A classification hierarchy is based on generalization and specialization. Base classes in such a hierarchy are very general, and their attributes few; the only thing that a class must do to qualify as food, for instance, is to provide nutrients.

As you move down the hierarchy, the derived classes become more specialized, and their attributes and behavior become more specific. Thus, although broccoli qualifies as food (it is, after all, digestible), it lacks the necessary qualifications to make it a dessert.

Week 12

Person<-Student

Inheritance introduces quite a few new possibilities into your programs. It is easy to miss some of the details that you really must master to make effective use of the object-oriented technique of inheritance.

So, instead of working with fun stuff, like card games and shooting down aliens, we'll start by returning to the old, boring "finger-exercise" example that lets you concentrate on one piece of the inheritance puzzle at a time.

The running-man icon. Click the Running Man to open the lab example in Replit. Fork the Repl so you'll have your own copy to work on.

Extending Person

In Java, you use the extends keyword to specify the parent or superclass (called the base class in C++) when you define the child or subclass (called the derived class in C++). Instead of using the extends keyword, as in Java, we use a colon in exactly the same position. In addition, we specify that the base class is public.

class Student : public Person
{
    // members of the derived class
};

Student is the derived class, while Person is the base class. Each of these class definitions is placed in its own header file, with the implementation of the member functions in person.cpp and student.cpp. You can open these in the Replit editor.

Week 12

The UML Diagram

The UML diagram for the Person class.

The Person class represents people, and Student is a new (specialized) kind of Person. On the right is the UML (Unified Modeling Language) class diagram for these classes.

Person is our base class. Each Person has:

  • a single data member, name, stored as a string. The minus sign preceding name tells us that the data member will be private.
  • one mutator, setName() that allows you to change the name of the Person.
  • one accessor, getName(), which allows you to retrieve the value of name.
  • The plus sign before the member functions indicates that they are public.
  • In each entry, the word appearing after the colon is the data member type, or member function return type.

The Student class is derived from Person.

  • In the UML diagram, the hollow-headed arrow pointing from Student to Person says Student is derived from the Person class.
  • Student has one private data member, studentID, stored as a long.
  • The class has a public constructor that takes two arguments.
  • The class has an accessor to retrieve the value in studentID.

There are no mutators to set or change the ID. While a student might change their name (because of marriage, for instance) they can never change their student ID.

Week 12

Inherited Members

Open main.cpp and look at the main() function, which creates a Student object (steve), and then call some of its member functions. Run the project by typing make run in the terminal. You'll see something like this:

./inherit
getName()->Stephen
getID()->1007

Of course the Student object named steve can call the getID() member function, which was defined in the Student class. No surprises there!

However, it can also call the setName() and getName() member functions, which were not defined in Student, but in Person. More importantly, those member functions can read and change the name data member in the Person class as if name were declared inside the Student class. Why?

A "logical" UML diagram of Student.

When you create Student objects, each derived class object contains all of the data members and member functions of its base class. If you were to look at a "logical" diagram of the Student class, it would look something like that shown here.

However, (very important), the data members will not be directly accessible to the derived class object, because they were declared private in the base class.

Week 12

Private Base-Class Members

Private base-class data members are not directly accessible to the derived class object member functions. To see how this works, in the editor, change the Student constructor so that it attempts to set the private name data member directly, (instead of using the setName() member function).

Student::Student(const string sname, long sid)
{
    // setName(sname);  // comment this out
    name = sname;       // add this;
    studentID = sid;
}

Type make in the console. You'll see an error message that looks something like this:

student.cpp:8:3: error: string Person::name is private here:
  name = sname;
  ^~~~

Even though you can't access the private name data member from the derived Student class, the data member exists inside the Student object nonetheless. You know that is true, because the inherited setName() and getName() member functions work correctly, and without the existence of the private data member, that would not be possible.

Private data members are inherited, in the sense that each derived class object contains a copy of each private variable defined in the base class, even though the member functions in the derived object are not free to directly access that variable.