I want to create a UML class diagram to represent a Kotlin class. I have come across a few options and came up with two alternatives. However, I'm unsure which of the two is the most appropriate and accurate. Can someone help me determine which UML diagram is correct or better for this Kotlin class?
Here is the Kotlin class I want to represent:
class Student(val name: String, var address: String) {
private var numCourses: Int = 0
private val courses: Array<String> = Array(MAX_COURSES) { "" }
private val grades: IntArray = IntArray(MAX_COURSES)
companion object {
const val MAX_COURSES = 30
}
fun printGrades() {
print(name)
for (i in 0 until numCourses) {
print(" ${courses[i]}:${grades[i]}, ")
}
println()
}
fun calculateAverageGrade() = grades.sum().toDouble() / grades.size
fun addCourseGrade(course: String, grade: Int) {
require(numCourses < MAX_COURSES) {
"A student cannot take more than $MAX_COURSES courses"
}
require(grade in 0..100) {
"Grade must be between 0 and 100"
}
courses[numCourses] = course
grades[numCourses] = grade
numCourses++
}
override fun toString() = "name($address)"
}
All the class' attributes are private and the public ones get public getters/setters:
I used these two answers on Stackoverflow for the <<get/set>>
annotations (see here) and {readOnly}
attribute (see here).
Let's keep it simple. I read that Kotlin properties are mutable when defined with var
and read only when defined with val
. I see in your code two public and three private properties.
Looking at the UML definition of a property and UML rules for accessibility, this translates very simply to:
+ name: String {readOnly}
+ address: String
- numCourses: Int=0
- courses: String[MAX_COURSES]
- grades: Int[MAX_COURSES]
Some more explanations:
{readOnly}
, it would apply to the all the MAX_COURSES elements, meaning that you could only define their values once and not change them afterwards (i.e. stay forever ""): this is not what you want to express.Additional remarks:
For a Kotlin property xyz
with getters and setters using a backing property, you do not need anything else than the standard UML notation of derived property: /xyz
. UML specifications explain that:
But where a derived Property is changeable, an implementation is expected to make appropriate changes to the model in order for all the constraints to be met, in particular the derivation constraint for the derived Property. The derivation for a derived Property may be specified by a constraint.
If you would define in your class explicit getters and setters and backing field, you could use «get»
, «set»
stereotypes to inform that there could be some operations behind the scene. For properties in interfaces, I'd do it systematically, to clarify what the implementing classes have to provide.