Let’s work through an example of creating a UML class diagram based on existing code. This is loosely based off a project from an earlier course, so some of the structure may be familiar.
The Project
This project is a number calculator that makes use of object-oriented concepts such as inheritance, interfaces, and polymorphism to represent different types of numbers using different classes. We’ll also follow the Model-View-Controller (MVC) architectural pattern.
Number Interface
We’ll start by looking at the Number
interface, which is the basis of all of the number classes. We’re omitting the method code in these examples, since we are only concerned with the overall structure of the classes themselves.
public interface Number {
Number add(Number n);
Number subtract(Number n);
Number multiply(Number n);
Number divide(Number n);
}
class Number(metaclass=abc.ABCMeta):
@classmethod
def __subclasshook__(cls, subclass: type) -> bool:
@abc.abstractmethod
def add(self, n: Number) -> Number:
@abc.abstractmethod
def subtract(self, n: Number) -> Number:
@abc.abstractmethod
def multiply(self, n: Number) -> Number:
@abc.abstractmethod
def divide(self, n: Number) -> Number:
In UML, we’d represent this interface using the following box. It includes the <<interface>>
stereotype, as well as the listed methods shown in italics since they are all abstract. Finally, each method in an interface is assumed to be public
, so we’ll include a plus symbol +
in front of each method.
Real Number Class
Next is the class for representing real numbers. This class will be a realization of the Number
interface, as we can see in the code:
public class RealNumber implements Number {
private double value;
public RealNumber(double value){ }
public Number add(Number n){ }
public Number subtract(Number n){ }
public Number multiply(Number n){ }
public Number divide(Number n){ }
@Override
public String toString(){ }
@Override
public boolean equals(Object o){ }
}
class RealNumber(Number):
def __init__(self, value: float) -> None:
self.__value = value
def add(self, n: Number) -> Number:
def subtract(self, n: Number) -> Number:
def multiply(self, n: Number) -> Number:
def divide(self, n: Number) -> Number:
def __str__(self) -> str:
def __eq__(self, o: object) -> bool:
it also includes implementations for a couple of other methods beyond the interface, including a constructor. So, in our UML diagram, we’ll add another box to represent that class, and use the realization association arrow to show the connection between the classes. Remember that the arrow itself points toward the interface or parent class.
Other Number Classes
From here, it’s pretty easy to see how we can use inheritance to create a RationalNumber
class and an IntegerNumber
class. The only way that they differ from the RealNumber
class are the attributes. So, we’ll quickly add those to our UML diagram as well.
Complex Numbers
At this point, we can add a new class to represent complex numbers. A complex number consists of two parts - a real part and an imaginary part. So, it will both implement the Number
interface, but it will also be composed of two RealNumber
attributes. Notice that we’re using RealNumber
as the attribute instead of the Number
interface. This is because we don’t want a complex number to contain a complex number, so we’re being careful about our inheritance. In code, this class would look like this:
public class ComplexNumber implements Number {
private RealNumber real;
private RealNumber imaginary;
public ComplexNumber(RealNumber real, RealNumber imaginary){ }
public Number add(Number n){ }
public Number subtract(Number n){ }
public Number multiply(Number n){ }
public Number divide(Number n){ }
@Override
public String toString(){ }
@Override
public boolean equals(Object o){ }
}
class ComplexNumber(Number):
def __init__(self, real: RealNumber, imaginary: RealNumber) -> None:
self.__real = real
self.__imaginary = imaginary
def add(self, n: Number) -> Number:
def subtract(self, n: Number) -> Number:
def multiply(self, n: Number) -> Number:
def divide(self, n: Number) -> Number:
def __str__(self) -> str:
def __eq__(self, o: object) -> bool:
In our UML diagram, we’ll add a box for this class. We’ll also add both a realization association to the Number
interface, but also a composition association to the RealNumber
class, complete with the cardinality of the relationship.
MVC Components
Once we’ve created all of our number classes, we can quickly create our View
and Controller
classes as well. They will handle getting input from the user, performing operations, and displaying the results.
public class View {
public View(){ }
public void show(Number n){ }
public String input(){ }
}
public class Controller {
private List<Number> numbers;
private View view;
public Controller(){ }
public void build(){ }
public void sum(){ }
public static void main(String[] args){ }
}
class View:
def __init__(self) -> None:
def show(self, n: Number) -> None:
def input(self) -> str:
class Controller:
def __init__(self) -> None:
self.__numbers: List[Number] = list()
self.__view: View = View()
def build(self) -> None:
def sum(self) -> None:
@classmethod
def main(self, args: List[str]) -> None:
In the code, we see that the Controller
class contains an attribute for a single View()
instance, and also a list of Number
instances. So, we’ll end up using a composition association between Controller
and View
, and an aggregation association between Controller
and the Number
interface.
This is a small example, but it demonstrates many of the important object-oriented concepts in a single UML diagram:
- The
Number
class is an interface and abstract class RealNumber
implements theNumber
class through a realization associationRationalNumber
andIntegerNumber
show direct inheritance through a generalization associationImaginaryNumber
contains twoRealNumber
instances, showing the composition association and a multiplicity of 2.- The
Controller
,View
andNumber
classes make up the various parts of an MVC architecture. - The
Controller
stores a list ofNumber
instances, demonstrating the aggregation association. - The
Controller
also contains a singleView
instance, which is another composittion association with multiplicity of 1.
Further Reading
UML is a very broad topic to cover in a single module, let alone a single class. For more information on building and reading UML diagrams, refer to these sources:
- UML Class Diagram Tutorial from Visual Paradigm
- UML Class Diagram Tutorial (Video) from Lucidchart
There are also many textbooks devoted to teaching UML concepts, as well as lots of examples online to learn from. The O’Reilly subscription through the K-State Libraries offers several books to choose from that can be accessed for free through this link: