10.10. Vet 1 Example

Time: 00:06:36 | Download: Large, Large (CC), Small | Streaming (CC) | Slides (PDF)

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.

A more complex UML class diagram with six classes as follows:
Owner
--
- name : string
--
+ Owner(n : string, s : string, c : string)
+ setPet(pet : Pet*) : void
+ display() : void

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

Pet
--
- name : string
--
+ Pet(name : string)
+ setOwner(o : Owner*)
+ display() : void

Dog
--
- akcNum : int
--
+ Dog(name : string, akc : int)
+ ~Dog()
+ setShots(y : int, m : int, d : int) : void
+ display() : void

Fish
--
- color : int--
+ Fish(name : string, c : int)
+ display() : void

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

An association relationship connects an Owner and a Pet.
Composition, named home in this example, connects an Owner (the whole class) with an Address (the part). 
Pet is a superclass, with Dog and Fish as subclasses.
Dog (the whole) and Date (the part) form an aggregation relationship.
Vet 1 class diagram. The example illustrates four UML class relationships: inheritance, composition, aggregation, and association. The inheritance relationships share a single arrowhead, making Dog and Fish different kinds of Pets. The composition relationship between an Owner and their Address makes the connection unrealistically permanent, but it allows the example to demonstrate the differences between composition and aggregation, the relationship between Dog and Date.

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.

#include "Owner.h"
#include "Dog.h"
using namespace std;

int main()
{
	Dog	myPet("Dogbert", 300);
	Owner	theOwner("Dilbert", "115 Elm St.", "Ogden");

	myPet.setShots(2000, 9, 1);			// establishes an aggregation between a Dog and a Date

	myPet.setOwner(&theOwner);			// establishes the Pet end of association
	theOwner.setPet(&myPet);			// establishes the Owner end of association

	theOwner.display();				// starts a sequence of function calls displaying all program data

	return 0;
}
vet.cpp. The vet driver has a simple main function. Class diagrams specify the general relationships between classes, but programs must establish an actual binding between objects. The vet example instantiates Owner and Pet objects and builds the association relationship, one end at a time, with the setOwner and setPet functions. C++ implements association with pointers, so the program finds the addresses of the objects with the address-of operator, &. Alternatively, the setShots function builds a new Date object, establishing the aggregation relationship using the function arguments.
Owner.hPet.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();
        }
};
#pragma once;

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

class Owner;					// (a)

class Pet
{
    private:
        string	name;
        Owner*	owner = nullptr;		// (b)

    public:
        Pet(string n) : name(n) {}

        void setOwner(Owner* o) { owner = o; }	// (d)

        void display()				// (e)
	{
            cout << "Pet: " << name << endl;
        }
};
 
 
 
 
 
 
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.
  1. 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.
  2. The variables implementing association.
  3. 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).
  4. 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.
  5. Association's bidirectionality demands caution when programmers chain function calls, such as the display function, to avoid runaway recursion.
Dog.hFish.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."
  1. Incorporates the superclass's specification
  2. Implements inheritance
  3. Calls the superclass constructor (highlighted)
  4. 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.
  1. Initializing shots supports the if-statements in setShots and display
  2. The destructor destroys the part object when the program destroys the whole, avoiding a memory leak
  3. The setter function tests for and destroys an existing part before creating a new one from the parameter data
Address.hDate.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.