10.6. Inheritance And Whole-Part: Actor 3

Time: 00:04:12 | Download: Large, Large (CC), Small | Streaming (CC) | Slides (PDF)
Review

Once we understand how to build and use class relationships, we can begin constructing programs incorporating many classes connected by any appropriate relationships. This version of the Actor example starts with the three-level inheritance hierarchy developed in the Actor 1 example and adds two classes illustrating two kinds of whole-part relationships. Class Address is embedded inside class Person while class Date is made a part of Person with a pointer. The UML class diagram in the following figure shows the relationships between all the program classes:

A complex UML class diagram with five related classes:

Date
--
- year : int
- month : int
- day : int
--
+ Date(y : int, m : int, d : int)
+ display() : void

Address
--
- street : string
- city : string
--
+ Address(s : string, c : string)
+ display() : void

Person
--
- name : string
--
+ Person(n : string, s : string, c : string)
+ Person(p : Person&)
+ ~Person()
+ setDate(y : ing, m : int, d : int) : void
+ display() : void

Actor : public Person
--
- agent : string
--
+ Actor(n : string, a : string, s : string, c : string)
+ display() : void

Star : public Actor
--
- balance : double
--
+ Star(n : string, a : string, b : double, s : string, c : string)
+ display() : void
A composition relationship connects the Person (the whole) and Address (the part) classes. An aggregation relationship connects Person (the whole) and Date (the part). Actor is a subclass of Person, and Star is a subclass of Actor.
UML class diagram with inheritance, composition, and aggregation. The hollow arrow connector denotes inheritance (the arrowhead attaches to the superclass and the plain end to the subclass). The solid diamond connector denotes composition created by embedding (the diamond attaches to the whole and the plain end to the part). Finally, the hollow diamond denotes aggregation created with a pointer (the diamond attaches to the whole and the plain end to the part). There are five important differences between the UML class diagram presented here and the first Actor class diagram:
  1. The Address class is added and forms a composition relationship with Person by embedding an Address in the Person class (i.e., Person is the whole class and Address is the part class)
  2. The Date class is added and forms an aggregation relationship with Person through a pointer member variable in the Person class (i.e., Person is the whole class and Date is the part class)
  3. A destructor, ~Person(), is added to Person to manage dynamic memory when a Person is destroyed
  4. A setter function, setDate, is added to the Person class to initialize the aggregation relationship
  5. The constructors for Star, Actor, and Person have additional arguments for the data passed up the inheritance hierarchy from Star to Person and is then used to initialize the Address object embedded in Person

The Actor 3 Part Classes

Class developers can implement a whole-part relationship with composition or aggregation. Both are one-way relationships, meaning that the part class "does not know about" the whole class. The source code for the part classes in this example reflects this situation by not referencing the whole class - the name Person does not appear in either class specification.

Address.h Date.h
#pragma once

#include <iostream>
#include <string>
using namespace std;

class Address
{
    private:
	string street;
	string city;

    public:
	Address(string s, string c) : street(s), city(c) {}

	void display()
	{
		cout << street << " " << city << endl;
	}
};
 
 
#pragma once

#include <iostream>
using namespace std;

class Date
{
    private:
	int year;
	int month;
	int day;

    public:
	Date(int y, int m, int d)
            : year(y), month(m), day(d) {}

	void display()
	{
            cout << year << " " << month <<
                " " << day << endl;
	}
};
The Actor 3 Address and Date classes. Address and Date are terminal classes in the sense that they are parts of the Person class but do not participate in other relationships. The program pushes data into them through their constructors and pulls them through their display functions, but the constructors and display functions are not chained (i.e., do not call) other problem-domain functions. (Actor 3 treats the string class as a fundamental or built-in type.)

Actor 3 Person class

#pragma once							// ensures the header file is only processed once

#include "Address.h"						// includes the part classes specifications
#include "Date.h"
#include <iostream>
#include <string>
using namespace std;

class Person
{
    private:
        string    name;
        Address    addr;					// builds a composition relationship
        Date* date = nullptr;					// builds an aggregation relationship

    public:
        Person(string n, string s, string c)			// a general constructor
            : addr(s, c), name(n) {}				// composition must be established when building the whole

        Person(Person& p)					// copy constructor
            : name(p.name),
              addr(p.addr),
              date(new Date(*p.date)) {}

        ~Person() { if (date != nullptr) delete date; }		// destructor

        void setDate(int y, int m, int d)			// aggregation setter
        {
            delete date;					// prevent a memory leak
	    date = new Date(y, m, d);				// install a new Date
        }

        void display()
        {
            cout << name << endl;				// name is a Person member
	    addr.display();					// uses composition
	    if (date != nullptr)				// it's an error to call a function with nullptr
	        date->display();				// uses aggregation
        }
};
Actor 3 Person.h file. In the Person/Address and Person/Date relationships, Person plays the role of the whole class, while it is the superclass to the Actor and Star classes. The example highlights composition-related code in coral and aggregation-related code in blue.
void Person::setDate(int y, int m, int d)
{
    if (date != nullptr)
        delete date;
    date = new Date(y, m, d);
}
void Person::setDate(Date* d)
{
    if (date != nullptr)
        delete date;
    date = d;
}
(a)(b)
Aggregation setter function options. Programmers can create two kinds of setter functions to manage aggregation:
  1. Creates a new Date object from "raw" data.
  2. Install an existing Date object passed in by pointer.
Although classes can include both versions as overloaded functions, they typically only need one, and the problem determines which is most appropriate. Modern C++ compilers make the if-test, if (date != nullptr), optional.

Actor 3 Inheritance

The inheritance syntax in the Actor 3 example is unchanged from Actor 1. However, the number of constructor parameters is different. The example begins when the program instantiates a Star object, passing the initializing data through a chain of constructor calls. In the current example, the data now includes values to initialize the part objects instantiated from Address and Date.

#pragma once

#include "Person.h"
#include <iostream>
#include <string>
using namespace std;

class Actor : public Person
{
    private:
        string agent;

    public:
        Actor(string n, string a, string s, string c) : Person(n, s, c), agent(a) {}

        void display()
        {
            Person::display();
            cout << agent << endl;
        }
};
The Actor 3 Actor class. The Actor class illustrates a class that is simultaneously a subclass (of Person) and a superclass (of Star). Inheritance is a one-way relationship, from the subclass to the superclass, so an Actor "knows about" a Person but not about a Star. Furthermore, Address and Date are only related and "known" to Person. The #include directives at the top of the header file reflect this "knowledge" or navigability: Person is the only application class specification Actor needs.
#pragma once

#include "Actor.h"
#include <iostream>
#include <string>
using namespace std;

class Star : public Actor
{
    private:
        double balance;

        public:
            Star(string n, string a, double b, string s, string c) : Actor(n, a, s, c), balance(b) {}

        void display()
        {
            Actor::display();
            cout << balance << endl;
        }
};
The Actor 3 Star class. Significantly, Star only "knows about" Actor. We see this "knowledge" reflected in the #include directives, the inheritance syntax, the chained constructor call, and the call to the Actor display function.

Downloadable Code

Tab stops are set at 4 spaces - the default for the Visual Studio editor.

ViewDownload
Address.h Address.h
Date.h Date.h
Person.h Person.h
Actor.h Actor.h
Star.h Star.h
Actor3.cpp Actor3.cpp