0

If I have a class that contains another class (through composition) which in turn, contains another class. For example: a Teacher class containing a PersonalDetails class, that contains a ContactInformation class.

ContactInformation class:

class ContactInformation
{
private:
    std::string m_email{};
    std::string m_phoneNumber{};

public:
    ContactInformation(const std::string &email, const std::string &phone)
        : m_email{ email }, m_phoneNumber{ phone }
    {
    }

    // Solution 1
    const std::string& getEmail() const { return m_email; }
    const std::string& getPhoneNumber() const { return m_phoneNumber; }

    // Solution 2
    const ContactInformation& getContactInfo() const { return *this; }

    // Solution 3
    friend class Teacher;
};

PeronalDetails class:

class PersonalDetails
{
private:
    ContactInformation m_contact;
    std::string m_name;

public:
    PersonalDetails(const ContactInformation &info, const std::string &name)
        : m_contact{ info }, m_name{ name }
    {
    }


    // Solution 1
    const std::string& getEmail() const { return m_contact.getEmail(); }
    const std::string& getPhoneNumber() const { return m_contact.getPhoneNumber(); }
    const std::string& getName() const { return m_name; }


    // Solution 2
    const ContactInformation& getContactInfo() const { return m_contact.getContactInfo(); }
    const PersonalDetails& getPersonalDetails() const { return *this; }

    // Solution 3
    friend class Teacher;
};

Teacher class:

class Teacher
{
private:
    PersonalDetails m_details;
    double m_salary{};

public:
    Teacher(const PersonalDetails &details, double salary)
        : m_details{ details }, m_salary{ salary }
    {
    }

    // Solution 1
    const std::string& getEmail() const { return m_details.getEmail(); }
    const std::string& getPhoneNumber() const { return m_details.getPhoneNumber(); }
    const std::string& getName() const { return m_details.getName(); }
    double getSalary() const { return m_salary; }

    void printEmail1() const 
    {
        std::cout << getEmail() << '\n';
    }


    // Solution 2
    const ContactInformation& getContactInfo() const { return m_details.getContactInfo(); }
    const PersonalDetails& getPersonalDetails() const { return m_details.getPersonalDetails(); }

    void printEmail2() const 
    {
        std::cout << getContactInfo().getEmail() << '\n';
    }


    // Solution 3
    const std::string& getTeacherEmail(const ContactInformation &c) const
    {
        return c.getEmail();
    } 

    void printEmail3() const
    {
        std::cout << getTeacherEmail(getContactInformation());
    }

};

What is the "proper way" for the Teacher class to access the members (m_email & m_phoneNumber) in ContactInformation (the most "nested" class)? Neither of the solutions I can come up with seem all that great.

Solution 1; is to have getters methods for the member variables in all of the classes. But this seems like a bad idea since the Teacher class will end up with a lot of getters methods. Especially if I were to add more classes in the future.

Solution 2; is to return the instance itself. I don't know if this is better or if it breaks any best practices. But you can use the instance in the Teacher class to call getEmail() on it.

Solution 3; is using friend classes (don't have a lot of experience using them). but since you still have to pass an instance of ContactInformation in order to get m_email. It doesn't seem much different from Solution 2.

Is there any way of making the Teacher class a friend (or something) with the ContactInformation class so I can do something like this:

teacher.getEmail(); 

Without having to have any getters except from the one in ContactInformation?

Henrik
  • 69
  • 1
  • 2
  • 4
  • 2
    I try to avoid getter/setter methods, and instead try to follow the "tell, don't ask" principle (TDA) or also known as "the hollywood princple" (don't call us, we'll call you) - in other words, don't have Teacher object passively hold data, instead have it have behavior. But if TDA just won't work in this case, I'd have the Teacher getter call it's member's PersonalDetails getter, and have the PersonalDetails getter call its member's ContactInformation getter. – Eljay Oct 13 '20 at 15:58
  • 1
    using getters and setters is the opposite of proper encapsulation. Instead of data structures that need to pass their internal data around, think of them as interacting objects. I don't remember in which talk I heard this great analogy: When you go to a bar, the waiter will not open your wallet, count all your money, subtract the price of a beer and then put the difference amount back in your wallet (but thats where this setter/getter mania leads to `customer.setMoney( customer.getMoney() - priceForOneBeer);`) – 463035818_is_not_an_ai Oct 13 '20 at 16:46
  • @idclev463035818 I should probably try to find some good resources about TDA, seems like it would really help me structure my programs better. But if I were to follow the "Tell, don't ask" principle, how would I restructure my program? It might have been a bad example having printEmail() in Teacher rather than in the ContactInfo class. But what if I have an external function that accepts an object of type ContactInformation, that does something with it. Should I instead try to fit that logic inside the ContactInfo class? – Henrik Oct 13 '20 at 19:06

1 Answers1

0

The problem with friends classes is that you will lose the posibility (in a future) of using ContactInformation for a different class than Teacher without really gaining much from that.

If PeronalDetails is a member of Teacher and ContactInformation is a member of PeronalDetails. you could simply teacher.personalDetails.contactInformation.m_email which is quite long and requires all these members being public.

A midlle point can be a personalized getter:

public:
Teacher::getEmail(){
    return personalDetails.contactInformation.m_email;
}
Ivan
  • 1,352
  • 2
  • 13
  • 31