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. With this understanding, we identify six classes and the relationships binding them together, 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 Owner and Pet.
Composition, named home, connects Owner (the whole class) with 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 of five UML class relationships: inheritance, composition, aggregation, and association. The example demonstrates how to translate a UML class diagram to C++ classes and implement, initialize, and use the relationships. One aspect of the association relationship is odd: The relationship is between the Owner and Pet classes, but the program instantiates an Owner, and it binds it to a Dog object, thus 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 reference. Strictly speaking, only one class needs a forward reference, 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 reference 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.
  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.