Imagine we are writing a program to manage a veterinarian's office. This veterinarian has a limited practice, only treating two kinds of pets: dogs and fish. Although this problem is unrealistically simplified and constrained, it demonstrates a program with more classes and class relationships than previous examples. A brief analysis identifies six classes connected with five relationships, as illustrated in the following UML class diagram.
The Vet example demonstrates translating a UML class diagram into C++ classes and the code implementing the relationships. The constructors and setter functions initialize the relationships and member variables. The display function demonstrates the general syntax for using the various relationships. One aspect of the association relationship is odd: The relationship is between the Owner and Pet classes, but the following program instantiates a Dog object and binds it an Owner, demonstrating the substitution of a subclass object in place of a superclass. Chapter 12 explains how substitutability benefits programs and how programmers use it to create elegant and sophisticated solutions.
Owner.h
Pet.h
#pragma once;
#include "Address"
#include "Pet.h"
#include <iostream>
#include <string>
using namespace std;
class Pet; // (a)
class Owner
{
private:
string name;
Pet* myPet = nullptr; // (b)
Address home; // (c)
public:
Owner(string n, string s, string c)
: name(n), home(s, c) {}
void setPet(Pet* p) { myPet = p; } // (d)
void display() // (e)
{
cout << "Owner: " << name << endl;
home.display();
if (myPet != nullptr)
myPet->display();
}
};
The vet association classes. Association is a bidirectional relationship, so each class "knows about" the other. C++ programs implement association with pointers, one in each class. Neither class defines a destructor because the program instantiates the associated objects as local variables in main and deallocates them when it terminates. Additionally, the program builds a composition relationship between a pair of Owner and Address objects. The example highlights association-related code in blue and composition-related code in coral.
A forward declaration. Strictly speaking, only one class needs a forward declaration, but crafting the correct sequence is often difficult and leads to "fragile" (easily broken and difficult to reuse) code. The best practice is to include a forward declaration in both classes.
The variables implementing association.
The variable implementing composition between an Owner and an Address (although an unrealistic example - it suggests the owner can never move - it demonstrates the correct relationship syntax).
A setter function that builds one end of an association relationship by binding the parameter to this object. Programmers can't define functions operating on instances of forwardly declared classes in class specifications, but the restriction doesn't apply to pointers.
Association's bidirectionality demands caution when programmers chain function calls, such as the display function, to avoid runaway recursion.
Dog.h
Fish.h
#pragma once;
#include "Pet.h" // (a)
#include "Date.h"
#include <string>
#include <iostream>
using namespace std;
class Dog : public Pet // (b)
{
private:
int akcNum;
Date* shots = nullptr; // (i)
public:
Dog(string name, int akc)
: Pet(name), akcNum(akc) {} // (c)
~Dog() { delete shots; } // (ii)
void setShots(int y, int m, int d) // (iii)
{
if (shots != nullptr)
delete shots;
shots = new Date(y, m, d);
}
void display()
{
Pet::display(); // (d)
cout << "AKC#: " << akcNum << endl;
if (shots != nullptr)
shots->display();
}
};
#pragma once;
#include "Pet.h" // (a)
#include <string>
#include <iostream>
using namespace std;
class Fish : public Pet // (b)
{
private:
int color;
public:
Fish(string name, int c)
: Pet(name), color(c) {} // (c)
void display()
{
Pet::display(); // (d)
cout << "Fish color: " << color << endl;
}
};
The vet subclasses. Subclasses "know about" their superclass; the highlighted code reflects that "knowledge."
Incorporates the superclass's specification
Implements inheritance
Calls the superclass constructor (highlighted)
Calls the superclass display function
Additionally, the Dog and Date classes form an aggregation relationship, with the former acting as the whole and the latter as the part.
Aggregation requires the destructor and the setter function, setShots.
Initializing shots supports the if-statements in setShots and display
The destructor destroys the part object when the program destroys the whole, avoiding a memory leak
The setter function tests for and destroys an existing part before creating a new one from the parameter data
Address.h
Date.h
#pragma once;
#include <string>
#include <iostream>
using namespace std;
class Address
{
private:
string street;
string city;
public:
Address(string s, string c)
: street(s), city(c) {}
void display()
{
cout << "Street: " << street <<
" City: " << city << endl;
}
};
#pragma once;
#include <string>
#include <iostream>
using namespace std;
class Date
{
private:
int year;
int month;
int day;
public:
Address(int y, int m, int d)
: year(m), month(m), day(d) {}
void display()
{
cout << year << "/" << month <<
"/" << day << endl;
}
};
The vet part classes. Neither part class "knows about" the other program classes. Consequently, their header files don't include other program headers, nor do their constructors or display functions chain to or call functions in the other classes.