UML-Diagramme mit Graphviz

Häufig benötigt man mehr oder weniger einfache UML-Klassendiagramm, um in Vorlesungen, Klausuren oder Aufgabenblättern Sachverhalte übersichtlich darzustellen. Besonders praktisch ist, wenn man die Diagramme generieren kann, anstatt sie zu zeichnen.

In meinen Vorlesungen zur Programmierung benötige ich häufig UML-Diagramme. Den größten Anteil haben hier die Klassendiagramme. Mit gängigen Grafikprogrammen oder auch spezialisierten UML-Editoren lassen sich diese Diagramme schnell zeichnen und in XML oder anderen, proprietären Formaten ablegen. Mein Ziel ist schon seit längerem die vollständige Versionierung meiner Vorlesungsinhalte per GIT und die Erzeugung aller benötigten Artefakte (Folien, Übungsblätter etc.) mit einem einzigen make all. Für die Übungsblätter ist mit LaTeX eine gute Lösung zur Hand, für die Folien habe ich einen eigenen Workflow auf Basis von Markdown implementiert, die Grafiken sind aber noch immer ein Problem. Eine generelle Lösung für die Grafiken ist zwar noch nicht in Sicht und wahrscheinlich auch nur schwer zu finden, aber zumindest für die UML-Klassendigramme bietet Graphviz einige interessante Möglichkeiten.

Das folgende UML-Diagramm ist mit Graphviz erstellt.

UML-Diagramm

Der Quelltext sieht dann wie folgt aus.

digraph G {
    fontname = "Bitstream Vera Sans"
    fontsize = 8
    rankdir = BT

    node [
        fontname = "Bitstream Vera Sans"
        fontsize = 8
        shape = "record"
    ]

    Vogel [ label=<{<b><i>Vogel</i></b><br/>\{abstract\}|<i>+fliegen()</i><br/>}> ]

    Ente [ label=<{<b>Ente</b><br/>|+fliegen()<br/>+schwimmen()<br/>+eierlegen()<br/>}> ]

    Flugzeug [ label=<{<b>Flugzeug</b><br/>|+fliegen()<br/>}> ]

    Wasserflugzeug [ label=<{<b>Wasserflugzeug</b><br/>|+fliegen()<br/>+schwimmen()<br/>}> ]

    Flieger [ label=<{&laquo;interface&raquo;<br/><b><i>Flieger</i></b><br/>|+fliegen()<br/>}> ]

    Schwimmer [ label=<{&laquo;interface&raquo;<br/><b><i>Schwimmer</i></b><br/>|+schwimmen()<br/>}> ]

    edge [ arrowhead = "empty"; style = "dotted" ]

    Ente -> Flieger
    Ente -> Schwimmer
    Flugzeug -> Flieger
    Wasserflugzeug -> Schwimmer

    edge [
        fontname = "Bitstream Vera Sans"
        fontsize = 8
        arrowhead = "empty"
    ]

    Wasserflugzeug -> Flugzeug
    Ente -> Vogel
}

Man sieht aber auf den ersten Blick, dass der Quelltext für Graphviz mit Boilerplate-Code überfrachtet ist und ebenfalls wieder schwer zu versionieren und zu editieren ist, da sich alle Methodendeklarationen und Klassenbeschreibungen in einer einzigen Zeile befinden.

Daher habe ich mir eine einfache domänenspezifische Sprache überlegt, aus der ich die Eingabe für Graphviz erzeuge. Diese lässt sich viel besser mit einer Versionsverwaltung Verwaltung und unterstützt das DRY-Prinzip deutlich besser.

Class { Vogel {abstract}
    +fliegen() {abstract}
}

Class { Ente
    +fliegen()
    +schwimmen()
    +eierlegen()
}

Class { Flugzeug
    +fliegen()
}

Class { Wasserflugzeug
    +fliegen()
    +schwimmen()
}

Interface { Flieger
    +fliegen()
}

Interface { Schwimmer
    +schwimmen()
}

Inheritance {
    Wasserflugzeug -> Flugzeug
    Ente -> Vogel
}

Implementation {
    Ente -> Flieger
    Ente -> Schwimmer
    Flugzeug -> Flieger
    Wasserflugzeug -> Schwimmer
}

Mit einem kleinen Makefile kann man nun die UML-Diagramme in Grafiken übersetzen.

OUTPUT_DIR = 00_Output

COMPILE = ./compile.sh
DOT = dot -T pdf

all: $(OUTPUT_DIR)  \
        $(OUTPUT_DIR)/beispiel.pdf

clean:
    rm -rf $(OUTPUT_DIR)

$(OUTPUT_DIR)/beispiel.dot: beispiel.uml
    $(COMPILE) $< $@

$(OUTPUT_DIR)/beispiel.pdf: $(OUTPUT_DIR)/beispiel.dot
    $(DOT) $< > $@

$(OUTPUT_DIR):
    mkdir $(OUTPUT_DIR)

.PHONY: clean all

Die aktuelle Implementierung der DSL ist in Java, schöner wäre eine Skriptsprache wie Ruby oder Perl für die man keine VM benötigt.