SEP200

Introduction: Part II

Compositions, Aggregations and Associations

Summary

Introduction to the second half of SEP200

Relationships Overview

Compositions

Aggregations

Associations

Casting

Introduction

About Myself

Electrical Engineer by trade

+20 years of experience in Software Development

Teaching this course for the sixth time!

Brazilian, Canadian, and French

Father of two little honey badgers tasmanian devils beautiful children

What we will Learn

  Compositions, Aggregations and Associations

  STL: Containers and Iterators

  Function Pointers and Lambda expressions

  Error Handling

  STL: Algorithms

  Smart Pointers

  Threads

Delivery Method

Lectures will be in person

Workshops will also be in person - submission online is possible, but not recommended

Blackboard will be used for assignments, for announcements, and for grading purposes

Course's website contains general info and slides

Evaluation

Four Labs (2.5%): In person labs

Five Quizzes (1.0%): Blackboard Tests

Two Assignments (15%): Presentations

One Final Exam (20%): in class pen-on-paper test

Composition Aggregations and Associations

Relationships

Inheritance is one type of relationship between different classes

We will now cover other types of relationships:

  Composition

  Aggregation

  Association

Overview of Relationships

Compositions: The composer object owns and controls the lifecycle of the component object

Aggregation: The aggregator has an instance of another class

Association: One class accesses or uses another class

Overview of Relationships

Rule of thumb

Compositions:
Objects with other objects as member variables

Aggregation:
Objects with pointers (arrays) to other objects

Association:
Objects that interact with other objects

Overview of Relationships

A composition is a special type of aggregation

An aggregation is a special type of association

Hence, these relationships can be seen in a diagram as shown in the next slide

Diagram of Relationships

Strenght of Relationships

These relationships have "levels" or "strenghts"

Composition is the strongest relationship

Aggregation is weaker: the aggregator does not determine the lifecycle of the aggregated

Association is the weakest relationship

Composition

Composition

It is a "has-a" relationship

One object is responsible for creating and destroying its component objects

Frequently used with member variables

Can also be used for pointers - as long as the parent controls the lifecycle

Composition

Example

#include <iostream>
#include <cstring>

const int N = 64;

class Name
{
private:
    char fname_[N];
    char lname_[N];

public:
    Name(const char *, const char *);
    ~Name();
    void display();
};

Name::Name(const char *fname, const char *lname)
{
    std::cout << "Calling Constructor for Name!"
              << std::endl;
    strncpy(fname_, fname, N);
    fname_[N - 1] = '\0';
    strncpy(lname_, lname, N);
    lname_[N - 1] = '\0';
}

Name::~Name()
{
    std::cout << "Calling Destructor for Name!"
              << std::endl;
}

void Name::display()
{
    std::cout << "Name: " << fname_ << " "
              << lname_ << std::endl;
}

class Person
{
private:
    Name name_;
    char dob_[N];
    char birth_gender_[N];

public:
    Person(const char *, const char *,
           const char *, const char *);
    ~Person();
    void display();
};

Person::Person(const char *fname, const char *lname,
               const char *dob, const char *birth_gender)
    : name_(fname, lname)
{
    std::cout << "Calling Constructor for Person!"
              << std::endl;
    strncpy(dob_, dob, N);
    dob_[N - 1] = '\0';
    strncpy(birth_gender_, birth_gender, N);
    birth_gender_[N - 1] = '\0';
}

Person::~Person()
{
    std::cout << "Calling Destructor for Person!"
              << std::endl;
}

void Person::display()
{
    std::cout << "----------------------------"
              << std::endl;
    std::cout << "Person Data: " << std::endl;
    name_.display();

    std::cout << "Birth gender: " << birth_gender_
              << std::endl;
    std::cout << "Date of birth: " << dob_ << std::endl;
    std::cout << "----------------------------"
              << std::endl;
}

int main()
{
    Person someone("John", "Doe", "01/01/1985", "male");

    someone.display();

    return 0;
}
              

Aggregation

Aggregation

Weaker version of composition

Still carries a sense of ownership

The aggregator is not responsible for creating and destroying its component objects

Frequently used with pointers and arrays of pointers

Aggregation

Example

// code showcasing aggregation
#include <iostream>
#include <cstring>

const int N = 64;

class Assignment
{
private:
    char name_[N];
    float marks_;

public:
    Assignment(const char *, float);
    ~Assignment();
    void display();
};

Assignment::Assignment(const char *name, float marks)
{
    std::cout << "Constructor for Assignment." << std::endl;
    strncpy(name_, name, N);
    name_[N - 1] = '\0';
    marks_ = marks;
}

Assignment::~Assignment()
{

    std::cout << "Destructor for Assignment." << std::endl;
    std::cout << "Deleting: " << name_ << std::endl;
}

void Assignment::display(){
    std::cout << "Assignment: " << name_ << std::endl;
    std::cout << "marks: " << marks_ << std::endl;
}

class Course
{
private:
    char code_[N];
    char name_[N];
    Assignment *assignments_[16] = {};
    int num_assignments_ = 0;

public:
    Course(const char *, const char *);
    ~Course();
    void add_assignment(Assignment *);
    void display_assignments();
};

Course::Course(const char * code, const char * name)
{
    std::cout << "Course has been created." << std::endl;
    strncpy(code_, code, N);
    code_[N - 1] = '\0';
    strncpy(name_, name, N);
    name_[N - 1] = '\0';
}

Course::~Course()
{
    std::cout << "Course has been deleted." << std::endl;
}

void Course::add_assignment(Assignment *assignment)
{
    assignments_[num_assignments_] = assignment;
    num_assignments_++;
}

void Course::display_assignments()
{
    std::cout << "Assignments list: " << std::endl;
    std::cout << "----------------------------"
              << std::endl;
    for (int i = 0; i < num_assignments_; i++)
    {
        assignments_[i]->display();
    }
    std::cout << "----------------------------"
              << std::endl;
    std::cout << "End of Assignments" << std::endl;
}

int main()
{
    Course course("SEP200", "Object-Oriented Programming");
    
    Assignment workshop01("Workshop01", 3.0),
               workshop02("Workshop02", 3.0),
               quiz01("quiz01", 1.5), 
               quiz02("quiz02", 1.5);

    course.add_assignment(&workshop01);
    course.add_assignment(&workshop02);
    course.add_assignment(&quiz01);
    course.add_assignment(&quiz02);

    course.display_assignments();

    return 0;
}
              

Association

Association

Weak relationship between otherwise unrelated objects

One class uses elements of another class

It does not involve ownership of any kind

Association

Example

#include <iostream>
#include <cstring>

const int N = 64;

class Course;

class Room
{
private:
    char name_[N];

public:
    Room(const char *);
    ~Room();
    friend class Course;
};

class Course
{
private:
    char code_[N];
    char name_[N];
    char room_name_[N] = {};

public:
    Course(const char *, const char *);
    ~Course();
    void book_room(Room *);
    void display_info();
};

Room::Room(const char *name)
{
    strncpy(name_, name, N);
}

Room::~Room()
{
    std::cout << "Destructor for Room." << std::endl;
}

Course::Course(const char *code, const char *name)
{
    strncpy(code_, code, N);
    code_[N - 1] = '\0';
    strncpy(name_, name, N);
    name_[N - 1] = '\0';
}

Course::~Course()
{
    std::cout << "Course has been deleted." << std::endl;
}

void Course::book_room(Room *room)
{
    strncpy(room_name_, room->name_, N);
    room_name_[N - 1] = '\0';
}

void Course::display_info()
{
    std::cout << "Course code: " << code_ << std::endl;
    std::cout << "Course name: " << name_ << std::endl;
    std::cout << "Room: " << room_name_ << std::endl;
}

int main()
{
    Course sep200("SEP200", "Object-Oriented Programming");
    Course btl200("BTL200", "Mathematics for Developers");

    Room c3032("C3032"), e2042("E2042");

    sep200.book_room(&c3032);
    sep200.display_info();

    btl200.book_room(&e2042);
    btl200.display_info();

    return 0;
}
              

Association

You can still have pointers to objects of other classes in associations

As long as there isn't a sense of ownership

In the code that follows, it is clear that the classes Course and Room do not own one another

Association

Example

#include <iostream>
#include <cstring>

const int N = 64;

class Course;

class Room
{
private:
    char name_[N];
    Course *course_;

public:
    Room(const char *);
    ~Room();
    void display_info();
    friend class Course;
};

class Course
{
private:
    char code_[N];
    char name_[N];
    Room *room_;

public:
    Course(const char *, const char *);
    ~Course();
    bool book_room(Room *);
    void display_info();
    friend class Room;
};

Room::Room(const char *name)
{
    strncpy(name_, name, N);
    this->course_ = nullptr;
}

Room::~Room()
{
    std::cout << "Destructor for Room." << std::endl;
}

void Room::display_info()
{
    std::cout << "Room name: " << name_ << std::endl;
    if (course_ == nullptr)
    {
        std::cout << "Room currently empty" << std::endl;
    }
    else
    {
        std::cout << "Course code: " << course_->code_
                  << std::endl;
        std::cout << "Course name: " << course_->name_
                  << std::endl;
    }
    std::cout << "----------------------------"
              << std::endl;
}

Course::Course(const char *code, const char *name)
{
    strncpy(code_, code, N);
    code_[N - 1] = '\0';
    strncpy(name_, name, N);
    name_[N - 1] = '\0';
    room_ = nullptr;
}

Course::~Course()
{
    std::cout << "Course has been deleted." << std::endl;
}

bool Course::book_room(Room *room)
{
    if (room->course_ == nullptr)
    {
        room->course_ = this;
        room_ = room;
        return true;
    }
    else
    {
        std::cout << "Room already booked" << std::endl;
        return false;
    }
}

void Course::display_info()
{
    std::cout << "Course code: " << code_ << std::endl;
    std::cout << "Course name: " << name_ << std::endl;
    if (room_ == nullptr)
    {
        std::cout << "Room: " << "TBD" << std::endl;
    }
    else
    {
        std::cout << "Room: " << room_->name_ << std::endl;
    }
    std::cout << "----------------------------"
              << std::endl;
}

int main()
{
    Course sep200("SEP200", "Object-Oriented Programming");
    Course btl200("BTL200", "Mathematics for Developers");

    Room c3032("C3032"), e2042("E2042");

    sep200.book_room(&c3032);
    btl200.book_room(&c3032);

    sep200.display_info();
    btl200.display_info();

    btl200.book_room(&e2042);
    btl200.display_info();

    c3032.display_info();
    e2042.display_info();

    return 0;
}
              

Casting

Casting

The compiler records the types of all variables

There are situations in which a variable needs to be recasted into a different type:

  Upcasting a derived object as a base object

  Downcasting a base object into a derived object

  Numerical conversions (e.g., int to double)

  API interfacing

Casting

The following methods are provided to recast types:

  static_cast: performs type conversion at compile-time

  dynamic_cast: performs type conversion at run-time

  const_cast: modifies const or volatile qualifier

  reinterpret_cast: type conversion without checks

Casting

Example of numerical conversion

#include <iostream>
#include <typeinfo>

int main()
{

    int num = 10;
    std::cout << "10/3 = " << num/3 << std::endl;

    double numDouble = static_cast<double>(num);
    std::cout << "10/3 = " << numDouble/3 << std::endl;
    
    std::cout << typeid(num).name() << std::endl;
    std::cout << typeid(numDouble).name() << std::endl;

    return 0;
}
              

Casting

Example of upcasting

#include <iostream>

class Animal {
public:
    virtual void makeSound() const {
        std::cout << "Some sound" << std::endl;
    }
};


class Dog : public Animal {
public:
    void makeSound() const override {
        std::cout << "Bark bark!" << std::endl;
    }

    void fetch() {
        std::cout << "Fetching ball!" << std::endl;
    }
};

int main() {
    
    Dog fido;
    //upcasting
    Animal *animal = (&fido); 

    animal->makeSound();
    //animal->fetch(); //error

    return 0;
}

              

Casting

Example of downcasting

#include <iostream>

class Animal {
public:
    virtual void makeSound() const {
        std::cout << "Some sound" << std::endl;
    }
};


class Dog : public Animal {
public:
    void makeSound() const override {
        std::cout << "Bark bark!" << std::endl;
    }

    void fetch() {
        std::cout << "Fetching ball!" << std::endl;
    }
};

int main() {
    
    Dog fido;
    //upcasting
    Animal *animal = dynamic_cast<Animal *>(&fido);
    //downcasting
    Dog *fido_recovered = dynamic_cast<Dog *>(animal); 

    fido_recovered->makeSound();
    fido_recovered->fetch();

    return 0;
}

              

Suggested Reading

Class Relationships

Casting