I'm modeling a part of Python/Qt GUI code which uses reflection to construct a collection of widgets, in a form. The code works like this:
- There is a
Model
which has attributes of various types, including (but not limited to)Foo
andBar
. These types have corresponding widget types to be used in the form (FooWidget
,BarWidget
). SmartForm
iterates over all attributes ofModel
. If an attribute's type appears in its_TYPE_MAP
, it creates the corresponding widget (by calling the type stored in the map) and links it to the attribute ofModel
.
This is my diagram so far:
I have two questions about depicting this in UML.
- Should I create relations between
SmartForm
and the attribute types (Foo
,Bar
) and if so, what kind? The only use of these types bySmartForm
is a type check (by means of Python'sisinstance()
). It doesn't call nor create any of these objects. - Is the
<<use>>
relationship the most appropriate one to depict thatSmartForm
looks at the attributes ofModel
and their types? - Is the compose relationship the most appropriate between
SmartForm
and the widgets? I feel like in the end, after everything has been setup, this is correct becauseSmartForm
owns these objects. But I could also use an<<instantiate>>
relationship. Or maybe both, but I think that adds unnecessary complexity to the diagram.
Side note: IMO there's only a thin line between compose and aggregate in Python, due to how objects are passed and the underlying reference counting (ownership can shift at runtime).
1 Answer 1
UML has no build-in support for introspection. In particular does not foresee the ability to pass the type as a parameter. You may consider the following alternatives:
- Extend UML metamodel, to add a type type (not a typo!) and other elements required for introspection. You could think for example of a
«Introspection»
stereotype for the usage dependency that you currently have. - Model a type representation. Make the python features used for introspection visible in your model or assume you could just bridge the gap.
- Use UML templates, the only type mapping mechanism natively supported by UML: specific
Widget<Foo>
,Widget<Bar>
or more generallyWidget<T>
would replace yourFooWidget
and the_TYPE_MAP
(which should btw be static). Personally, I would see this as a very elegant approach that hides the implementation details. It allows however only one widget per type, and might mislead readers who see this as a rigid compile-time structure (even if the UML specs are agnostic in this regard). - Focus on the design intent and abstract even more the mapping.
Here dome details about this last option, that would allow to understand the logic but implement it also for languages not supporting introspection:
FooWidget
andBarWidget
realise some generalWidget
interface (or if working with Python, an abstract class would do as well). We guess it currently through the naming, but interface realization would make it explicit.- In view of the SRP / separation of concerns, make a distinction between the
SmartForm
construction and the form itself. For this purpose, you could use a factory for creating theWidgets
based on the model (Widgets appear to be a good candidate for composite pattern in view of the object composition principle). - The Model would be made of instances of an interface
ModelProperty
- Highlight the
«Create»
or the«Instantiate»
dependency to clarify that the widgets are created by the factory. - No need to attach the
Foo/Bar
properties to theSmartform
since it's an indirect association. - Simplify the relationship between
XxxWidget
andXxx
getting rid of aggragation (aggregation has no longer a defined semantic in UML, and this since over twenty years). - Composition is ok as you did. But do put it only if the lifecycle control really matters. if not go for a simple association.
The advantage of this design, is that it leaves out the implementation details. In Python or Java, you coud use introspection, but in C++ or Rust, you'd have to go for other alternatives. But the core idea of the design remains the same and is well explained.
-
1@PhilipKendall Indeed! Thanks, completed :-)Christophe– Christophe2024年01月23日 20:26:42 +00:00Commented Jan 23, 2024 at 20:26
SmartForm
must be aware ofFoo
andBar
, I would add an association between them. However it usually depends on what you want to communicate with the diagram. Maybe a note would already be sufficent to not clutter the diagram. Regarding your second point, how does the SmartForm parse the model? I assume it holds a reference so an association would be reasonable.