Skip to content

Pyreverse: composition / aggregation arrow strange behavior (and field annotation bug) #9045

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
3 tasks
nickdrozd opened this issue Sep 18, 2023 · 4 comments
Open
3 tasks
Labels
Bug 🪲 Discussion 🤔 pyreverse Related to pyreverse component

Comments

@nickdrozd
Copy link
Contributor

nickdrozd commented Sep 18, 2023

I came across a diagram that seemed to have too many arrows and too many kinds of arrows. The code is something like this:

class P:
    pass

class A:
    x: P

class B:
    def __init__(self, x: P):
        self.x = x

class C:
    x: P

    def __init__(self, x: P):
        self.x = x

Classes A, B, and C each have the same relationship to P, namely that they each have a field of that type. So it seems to me that they should all look similar in the class diagram.

But instead, they look like this:

classes

Class A has a "composition" arrow (black diamond), class B has an "aggregation" arrow (white diamond), and class C has both.

Is this correct from a UML perspective? To me this output is unexpected and undesirable. I've tried reading about it, but everything I can find about "aggregation vs composition" turns into word soup and I can't make sense of it. Plus all the examples are from Java, with fine distinctions that don't seem to apply in Python. (This SO page is the best discussion I've been able to find.)

On top of all that, the x field is unannotated in all of A, B, and C, when it should say x: P.

Tasks

Preview Give feedback
No tasks being tracked yet.

Tasks

Preview Give feedback
@Pierre-Sassoulas
Copy link
Member

Nice catch ! I think we should display only the strongest of the two relations for C. In this case I would argue it's composition (i.e. you can't instantiate a C without the contained P, which is stronger than association). But I'm far from an UML expert.

@DudeNr33
Copy link
Collaborator

Not a UML expert either, but as I understand it the example is not composition, so in all of these cases it should show just the aggregation arrow:

Composition implies a relationship where the child cannot exist independent of the parent. Example: House (parent) and Room (child). Rooms don't exist separate to a House.

(from this article)

Composition arrows should only be used if the parent (holding) class instantiates the P itself, which is not the case here.

That being said I agree with Pierre that if there are multiple arrows (for the same attribute as it is denoted on the arrow) we should only show the strongest relation.

@nickdrozd
Copy link
Contributor Author

So the logic is: A -> B is composition if A actually instantiates B, and aggregation otherwise? In examples:

class P:
    pass

class A:
    x: P  # can't tell, so default to aggregation

class B:
    def __init__(self, x: P):
        self.x = x  # not instantiated, so aggregation

class C:
    x: P

    def __init__(self, x: P):
        self.x = x  # not instantiated, so aggregation

class D:
    x: P

    def __init__(self):
        self.x = P()  # instantiated, so composition

class E:
    def __init__(self):
        self.x = P()  # instantiated, so composition

@DudeNr33
Copy link
Collaborator

At least that is my understanding from the article linked above, yes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug 🪲 Discussion 🤔 pyreverse Related to pyreverse component
Projects
None yet
Development

No branches or pull requests

3 participants