Search code examples
c++umlinner-classesclass-diagramc++03

UML Class Diagram with Struct Defined Inside Class


How would a UML diagram look like for the case of the class containing structs which only exist during the life-time of the class?

Disclaimer: I saw that there are similar questions, but mine differs in the fact, that the structs I use I define inside the class as described here. The examples I saw on how to use struct in UML always referred to specific struct to that class.
Please see 4 questions below first.

Sample Code

Parent.h

class Parent{
    private:
        uint8_t age;

        struct{
            uint16_t stepsADay;
            uint8_t levelsADay;
        }healthInfo;

    public:
        int setAge(uint8t_t a_age);
        uint8_t getAge();

        int setHealthInfo(uint16_t a_stepsADay,uint8_t a_levelsADay);
        struct healthInfo getHealthInfo();
};

Parent.cpp

   // omitting (de-)/constructor for readability
   int Parent::setAge(uint8t_t a_age){
       this->age = a_age;
       return 0;
   }
   uint8_t Parent::getAge(){
       return this->age;
   }

   int Parent::setHealthInfo(uint16_t a_stepsADay,uint8_t levelsADay){
       this->healthInfo.stepsADay = a_stepsADay;
       this->healthInfo.levelsADay= a_levelsADay;
       return 0;
   }
   struct healthInfo Parent::getHealthInfo(){
       return this->healthInfo;
   }

main.cpp

#include "Parent.h"

uint32_t createMessage(Parent *thatParent){
   healthInfo tmpHealthData=thatParent.getHealthInfo();

 // create message to give to doctor
  uint32_t messageToDoctor = (age <<24)
                             + (tmpHealthData.stepsADay<<8)
                             + (tmpHealthData.levelsADay);
 return messageToDoctor;
}

int main() {
 Parent papa = new Papa();

 papa.setAge(54);
 papa.setHealthInfo(1000,6);

 // do other stuff ...

 uint32_t message= createMessage(papa);
 // send message ... 

 return 0;
}

UML

To me it seems logical, that a struct is somehow similar to a class, but as I only use it w.r.t. to the class (when I don't interact with the Parent, I don't need that context at all). Technically I call that struct for a short moment in the createMessage method but only there.

I want to store important data in a meaningful matter until I need it to serializing this information to use it somewhere else.

I have multiple different structs similar to this healthInfo example.

enter image description here

Concerns

  1. When creating 1 UML class per «struct», I am sceptical that this actually improves the overall readability in the overall UML diagram. However, if this is demanded, would it look like the one above? I am unsure regarding the healthInfo attribute in Parent. I would say it is an aggregation,because the struct cannot exist without the Parent class.

  2. Is it wrong to use the struct as return type when it is defined in the class?

  3. Is it wrong to create structs inside the class in the first place?

  4. Once I return the struct as a whole, I want to access the attributes of the struct without getter methods. Is this fine or should I set them as private with getter methods to make it "cleaner" coding?

I assume there are some polarizing opinions, looking forward to the reasoning.


Solution

  • In short

    If this design would work, you would need to define two distinct relationships between the two classes HealthInfo and Parent:

    • a first containment relationship (circled plus) as Jean-Baptise explains. This tells that the definition of the UML class HealthInfo is nested within Parent.

    • a second composite aggregation (black diamond), that tells Parent has a member healthInfo and that this member's lifecycle is managed by Parent (this is due because C++ uses a value semantic unless you explicitly want a reference/pointer).

    Showing only one of these relationship does not capture appropriately your design.

    However nested private types that are leaked outside tend to cause hidden couplings. A public nested type or even better a non-nested type (in a separate namespace that encloses all the related classes) are better alternatives.

    More details, including design issues

    Some C++ issues in your code

    In C++ a struct is a class, the only difference being that members and base classes are public by default. There is no need to define and use a «struct» stereotype considering that you have shown public access for all the members.

    Edit: In a C++ implementation, the difference between a class and a struct is only accessibility of members and base class. In UML you have specified public members, and inheritance will always be public (there is no private inheritance) . In this regard, class X {public: .....}; and a struct X {...}; would have the same semantic and the same model. This is why I said there is no need to add a non-standard «struct» stereotype. Of course, if you use a C++ profile and the UML stereotypes are used for controlling code-generation, it makes sense to use «struct». Same if you want to have a clear mapping between design and implementation.

    There may however be a confusion in your code between the C++ and C usage of struct. The following specifies an anonymous struct (type) and defines a member healthInfo of that type:

      struct{
            uint16_t stepsADay;
            uint8_t levelsADay;
      }healthInfo; 
    

    You can therefore not return any healthInfo type, simply because you didn't define a name for that type. The compiler flags this problem with an error message.

    If you want to define a private type and return a value of that type, it should look like, as Eljay explained in the comments:

      struct HealthInfo {
            uint16_t stepsADay;
            uint8_t levelsADay;
      }healthInfo; 
    

    and the return type would be specified like this:

      HealthInfo Parent::getHealthInfo(){
       return this->healthInfo;
      }
    

    To avoid this kind of confusion between types and members, the recommended approach would be to define the type struct HealthInfo { ...}; and the member HealthInfo healthInfo; in two statements even if it's in the same Parent class.

    More serious design issues

    The problem with a nested private type that you use for return, is that the use outside of your class, in other class, or self-standing functions is challenging:

    Since getHealthInfo() returns a type that is not known outside of Parent (private is private), the other classes or non-member functions simply cannot do much with this return value. It cannot for instance create a local variable Parent::HeathInfo x = .... Fortunately the modern C++ has brought auto, so that you could write :

    auto x = papa.getHealthInfo(); 
    cout << x.stepsADay;
    

    But this is very annoying, because the auto gives you access to internals that should not be used outside the class. So this defeats the purpose of having a private nested type and creates hidden couplings. If later you would decide to change the private type, instead of having an impact limited to the class, you would have to inspect all the code using the parent class to find out if they use the private type's internals.

    Edit: with older C++ standards that predate C++11, the auto keyword means automatic storage duration (e.g.. a local variable of a function) and not automatic type deduction. In that case the private return type is completely useless

    Here the corrected code that compiles

    Alternatives ?

    Why nest the information and make it private if it is to be used outside, because of the return type? If you want to use this type elsewhere, the better approach would be to make the nested type public, and keep the member private, which is easily done if you apply the advices above. So that at least the dependencies are clear and clean.

    Another improvement would be to make createMessage() a member function of Parent.