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:
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; } }; |
#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
}
};
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) |
if (date != nullptr)
, optional.
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; } };
: public Person
.Person(n, s, c)
.Person::display();
.#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; } };
Tab stops are set at 4 spaces - the default for the Visual Studio editor.
View | Download |
---|---|
Address.h | Address.h |
Date.h | Date.h |
Person.h | Person.h |
Actor.h | Actor.h |
Star.h | Star.h |
Actor3.cpp | Actor3.cpp |