====== Grafische Programmierung mit der UML ====== Mit objektorientierten Programmiersprachen hat der Entwickler mächtige Sprachmittel, um komplexe Systeme realisieren zu können. C++ ist eine weit verbreitete objektorientierte Programmiersprache. Als Visualisierungsmittel objektorientierter Programme gilt die international standardisierte Beschreibungssprache UML (Unified Modeling Language). SiSy bietet dem Entwickler das UML-Klassendiagramm mit Codegenerierung für unterschiedliche Plattformen, unter anderem auch für AVR- und ARM-Mikrocontroller. Der folgende Abschnitt beschreibt die Grundelemente des Klassendiagramms in SiSy. ===== UML in der Entwicklungsumgebung SiSy ===== >>{{uml:uml_kd_sq_editor.jpg?750|}} Die folgende Abbildung zeigt Ihnen eine Kurzübersicht der Modellierungselemente des UML-Klassendiagramms. >>>{{:uml:kurzuebersichtkd.jpg|}} **Darstellung von Attributen:** \\ Attribute beginnen mit einem Kleinbuchstaben. Sichtbarkeit name : Typ = Initialwert {Merkmal} # temperatur : uint8_t = 25 **Schreibweise von Operationen:** \\ Operationen beginnen mit einem Kleinbuchstaben. Sichtbarkeit name (Parameter:Typ = Standardwert, ...) : Rückgabetyp {Merkmal} + setTemperatur ( temp : integer = 25 ) : bool ===== Abstraktion und Klassen ===== Eine Klasse ist in der Programmierung der Name und die Beschreibung der Attribute und Operationen eines bestimmten Typs von Systembausteinen. Die UML fordert, eine Klasse als Rechteck darzustellen. Der Name der Klasse soll mit einem Großbuchstaben beginnen. >>>{{uml:smd32pin.jpg?100|}} {{uml:klassecontroller.jpg|}} Jeder Controller kann eingeschaltet werden und soll dann arbeiten. Das sind Operationen, die der Controller ausführen soll. Diese können in der UML der Klasse zugeordnet werden. Operationen erscheinen als Liste im Klassenrahmen. >>> {{uml:controllerrun.jpg|}} ===== Objekte ===== Objekte sind in der Programmierung Instanzen von Klassen. In der UML werden Objekte ebenfalls als Rechteck dargestellt. Die Kennzeichnung als Instanz erfolgt durch Unterstreichen des Namens. Zusätzlich kann der Typ der Instanz angezeigt werden. Die Instanzbeziehung zwischen einem Objekt und seiner Klasse wird als Abhängigkeit (gestrichelte Linie mit offenem Pfeil) und dem Stereotyp <<instanceOf>> dargestellt. >>>{{uml:instanceof.jpg|}} In der gezeigten UML-Darstellung wurde Folgendes festgelegt: * Die Klasse //Controller// verfügt über die Operationen //powerOn// und //run//. * Es gibt ein globales Objekt mit dem Namen //system// vom Typ //Controller//. * Das Objekt //system// ist eine Instanz der Klasse //Controller//. ===== Vererbung ===== Klassen können Eigenschaften von anderen Klassen erben. Die Verwendung von Klassenbibliotheken und der darin enthaltenen Klassen, als Basisklassen der eigenen Anwendung, beschleunigen die Entwicklungsarbeit enorm. Bei der Vererbung kann man auch je nach Lesart von einer Generalisierung (von unten nach oben gelesen) oder einer Spezialisierung (von oben nach unten gelesen) sprechen. Eine Generalisierung wird in der UML als Voll-Linie mit einem großen nicht ausgemalten Pfeil zur Basisklasse dargestellt. Die Eselsbrücke für die korrekte Richtung des Pfeils lautet //"ist ein"//. >>>{{uml:vererbungstm32.jpg|}} In der gezeigten UML-Darstellung wurde Folgendes festgelegt: * Die Klasse //Controller// verfügt über die Operationen //powerOn// und //run//. * **Der Controller //"ist ein"// STM32F4** * **Es wird zugesichert, dass die Basisklasse STM32F4 aus dem Paket ARM::MCU stammt.** * Es gibt ein globales Objekt mit dem Namen //system// vom Typ //Controller//. * Das Objekt //system// ist eine Instanz der Klasse //Controller//. ===== Realisierung ===== Die UML kennt ein zweites Ausdrucksmittel für den Sachverhalt //"ist ein"//. Es gibt zahlreiche Anwendungsfälle, bei denen Vorlagen, Muster verwendet oder Vorschriften eingehalten werden sollen. So etwas können Struktur- oder Verhaltensmuster, aber auch Schnittstellendefinitionen sein. Da es sich hierbei nicht um eine Vererbung im eigentlichen Sinne handelt, wird zwar derselbe Pfeiltyp verwendet, aber die Linie wird gestrichelt dargestellt. In der UML spricht man von einer Realisierung. >>>{{uml:realisierungarmapp.jpg|}} In der gezeigten UML-Darstellung wurde Folgendes festgelegt: * Die Klasse //Controller// verfügt über die Operationen //powerOn// und //run//. * Der //Controller// ist ein //STM32F4// und **realisiert ein //AppKernel//** * Es wird zugesichert, dass die Basisklasse //STM32F4// aus dem Paket //ARM::MCU// stammt. * **//AppKernel// ist ein //Template// (Muster) aus dem Paket //ARM::App//** * Es gibt ein globales Objekt mit dem Namen //system// vom Typ //Controller//. * Das Objekt //system// ist eine Instanz der Klasse //Controller//. ===== Kapselung ===== Objektorientierte Programmiersprachen kennen verschiedene Konzepte, um die Stabilität von Anwendungen sicherzustellen. Eines der Konzepte ist die Kapselung. Dabei ist es möglich, Elementen, z. B. Attributen und Operationen von Klassen, sogenannte Sichtbarkeiten zuzuordnen. Damit kann verhindert werden, das geschützte Elemente unberechtigt benutzt werden. Die meisten Programmiersprachen unterstützen dies durch entsprechende Schlüsselworte wie //public//, //protected// und //privat//. Die UML bietet Symbole, welche zwischen den Sichtbarkeiten //+ public//, //~ package//, //# protected// und //- privat// unterscheiden. Die Sichtbarkeit wird bei Operationen und Attributen dem Namen vorangestellt. >>> {{uml:grundgeruestarm.jpg|}} In der gezeigten UML-Darstellung wurde Folgendes festgelegt: * Die Klasse //Controller// verfügt über die Operationen //powerOn// und //run//. * **Die Operation //powerOn// ist öffentlich und kann von außen aufgerufen werden.** * **Die Operation //run// ist geschützt und kann nur aus der Klasse heraus aufgerufen werden.** * Der //Controller// ist ein //STM32F4// und realisiert ein //AppKernel// * //AppKernel// ist ein //Template// (Muster) aus dem Paket //ARM::App// * Es wird zugesichert, dass die Basisklasse //STM32F4// aus dem Paket //ARM::MCU// stammt. * Es gibt ein globales Objekt mit dem Namen //system// vom Typ //Controller//. * Das Objekt //system// ist eine Instanz der Klasse //Controller//. Zusätzlich sind in dieser Darstellung die Rückgabetypen der Operationen auf //void// festgelegt worden. Dieses UML-Klassendiagramm kann jetzt in Quellcode überführt werden. Dieser kann, hier als vereinfachter Ausschnitt, so aussehen: >>><code cpp> // SiSy UML C++ Codegenerator //////////////////////////////////////////////// class Controller : public STM32F4 // implements AppKernel { public: Controller(); // automatisch generierter Konstruktor ~Controller(); // automatisch generierter Destruktor void powerOn(); protected: void run(); // aus dem Template AppKernel virtual void startApp(); virtual void onSysTick(); }; ////////////////////////////////////////////////////////////////////////////// // Instanz system der Klasse Controller Controller system; ////////////////////////////////////////////////////////////////////////////// </code> Der Stand bis an diese Stelle entspricht dem Grundgerüst eines UML-Projektes in SiSy. Das Werkzeug kümmert sich dabei um solche Details wie //includes//, //defines//, etc. Der Entwickler arbeitet im Klassendiagramm. Den generierten Quellcode muss er sich eigentlich nicht mehr als Ganzes anschauen. Das UML-Projekt ist sein "Source", die generierten Quellcodedateien nur noch temporäre Zwischenprodukte beim Bilden. Übrigens stellt Ihnen das Werkzeug SiSy derartige Grundgerüste an den entsprechenden Punkten der Projekterarbeitung zur Auswahl. Diese muss sich der Entwickler nicht jedes mal neu aufbauen. ===== Aggregation und Komposition ===== Systeme bestehen aus Komponenten, die Komponenten aus Bausteinen, diese wiederum aus Einzelteilen usw. Diese Ganz-Teil-Struktur lässt sich in der UML als Aggregation bzw. Komposition abbilden. Dabei wird durch den oben angesprochenen Codegenerator solch eine Aggregation als Attribut im Code abgebildet. {{uml:ledaggregation.jpg|}} In der gezeigten UML-Darstellung wurde Folgendes festgelegt: * Die Klasse //Controller// verfügt über die Operationen //powerOn// und //run//. * Die Operation //powerOn// ist öffentlich und kann von außen aufgerufen werden. * Die Operation //run// ist geschützt und kann nur aus der Klasse heraus aufgerufen werden. * Der //Controller// ist ein //STM32F4// und realisiert ein //AppKernel// * //AppKernel// ist ein //Template// (Muster) aus dem Paket //ARM::App// * Es wird zugesichert, dass die Basisklasse //STM32F4// aus dem Paket //ARM::MCU// stammt. * Es gibt ein globales Objekt mit dem Namen //system// vom Typ //Controller//. * Das Objekt //system// ist eine Instanz der Klasse //Controller//. * **Die Klasse //Controller// besitzt einen digitalen Ausgang.** * **Die Klasse //DigitalOut// wird unter dem Namen //led// in der Klasse Controller als öffentliches Attribut aggregiert.** <code cpp> // SiSy UML C++ Codegenerator //////////////////////////////////////////////// class Controller : public STM32F4 // implements AppKernel { public: DigitalOut led; // Aggregation des Bausteins public: Controller(); // automatisch generierter Konstruktor ~Controller(); // automatisch generierter Destruktor void powerOn(); protected: void run(); // aus dem Template AppKernel virtual void startApp(); virtual void onSysTick(); }; ////////////////////////////////////////////////////////////////////////////// // Instanz system der Klasse Controller Controller system; ////////////////////////////////////////////////////////////////////////////// </code> Die Aggregation entspricht also einem Attribut der Klasse. Somit ist die folgende UML-Darstellung letztlich genau dasselbe. Die Attributdarstellung spart Platz, ist aber weniger übersichtlich was die Systemarchitektur betrifft. {{uml:ledattribut.jpg?550|}} ===== Erster UML Versuch mit SiSy ===== Für diesen Schritt müssen Sie eine Version von SiSy ARM mit UML Add-On installiert haben. Das sollte jedoch ohne Ausnahme der Fall sein. Starten Sie SiSy und legen Sie ein neues Projekt mit dem Namen "//BeispielUML//" an. {{gallery>:uml?projekt0*.jpg&=11&100x80&lightbox }} Wählen Sie das **ARM-Vorgehensmodell**. Schließen Sie die Hardware an. Dann laden Sie bitte die Vorlagen **ARM C++ Framework**. Legen Sie ein **Klassendiagramm** mit dem Namen "//test1//" an, indem Sie das entsprechende Objekt per Drag & Drop aus der Objektbibliothek in das Diagramm ziehen. Überprüfen Sie die korrekten Einstellungen der **Zielsprache ARM C++**. Achten Sie auf die Einstellungen der Zielhardware unter **Extras ARM** für das **STM32F4 Discovery**. Wählen Sie auf dem Klassendiagramm rechte Maustaste **nach unten (öffnen)**. >>>{{uml:projektnu.jpg?700|}} Aus der Liste der angebotenen Diagrammvorlagen wählen Sie bitte **Grundgerüst für AppKernel-Applikation**. >>>{{uml:projekt11.jpg?700|}} Vergleichen wir die soeben geladene Vorlage mit unseren Vorüberlegungen, so finden wir hier die gewünschte Operation für die Startsequenz und die Laufzeitoperation für den Controller. Desweiteren ist die Instanz der Anwendungsklasse zu erkennen und das die Klasse Controller die Merkmale eines //AppKernel// realisiert. Übrigens kann man sich das recht nett anschauen, was mit dieser scheinbar schmucklosen Realisierungsbeziehung bereits an vorgeferigter Funktionalität bereitgestellt wird. Sie können das Template //AppKernel// selektieren und über das Rechte-Maustasten-Menü zum **Quelldiagramm** des Templates gelangen. >>>{{uml:projekt12.jpg?400|}} Wenn wir das im oben begonnenen Stil fortsetzen, können wir zu der geladenen Vorlage folgende Aussagen treffen: * Die Klasse //Controller// verfügt über die Operationen //onStart// und //onWork//. * Die Operationen sind geschützt. * Es gibt ein globales Objekt mit dem Namen //app// vom Typ //Controller//. * Das Objekt //app// ist eine Instanz der Klasse //Controller//. * Der Controller realisiert ein //AppKernel// * AppEventKernel ist ein Template (Muster) aus dem Paket temposARM_App * //AppKernel// ist ein AppModul * Die Klasse //AppModul//: * ist ein //LinkedListElement// (verkettete Liste) * besitzt unter anderem die Operation //onStart// * die Operation //onStart// ist virtuell und kann durch Ableitungen überschrieben werden (Polymorphie) * Ein //AppKernel// verfügt über eine Reihe von Laufzeitoperationen wie * //onStart, onWork, onSysTick,// usw. * virtuelle Operartionen (kursiv gestellt) können durch den Anwendungsprogrammierer überschrieben werden. * Ein //AppKernel// kennt eine verkettete Liste von //AppModulen// * //AppModule// verfügen ebenfalls bereits über wichtige Laufzeitoperationen Mit ein paar UML-Kenntnissen kann man das alles und noch viel mehr nur aus den obigen Darstellungen entnehmen. Das Klassendiagramm ist die Konstruktionszeichnung einer Anwendung. Vergleichen Sie diese Beschreibung doch einfach mal mit den genannten Darstellungen. Mit diesem UML-Grundgerüst wollen wir jetzt weiter arbeiten. Als Nächstes schließen wir eine LED an den Controller an. Ziehen Sie eine Klasse aus der Objektbibliothek in das Diagramm und geben dieser den Namen //Led//. Verbinden Sie die //LED//, ausgehend vom //Controller//, mit einer Aggregation. Die Verbindung wird als Attribut im Controller abgebildet. Der Rollenname //+led// wird das öffentliche Attribut //led// vom Typ //Led//. >>>{{uml:neueLED.JPG?700|}} ===== Templates ===== Templates (engl. Schablonen, Vorlagen) sind aus der Programmierung, zum Beispiel von generischen Klassen, bekannt (Klassen, die Klassen generieren). Es geht also um Vorlagen, die zum Übersetzungszeitpunkt etwas Konkretes erstellen. SiSy kennt solche generischen Strukturen als //Templates//. Die Generierung der konkreten Klassen übernimmt dabei der Codegenerator. Es muss also nicht auf generische Klassen auf Compilerebene zurückgegriffen werden. Die Templates dienen als Struktur- und Verhaltensmuster. Es wird keine Vererbung zu diesen Vorlagen erzeugt, sondern die in den Templates vorgefertigten Struktur- und Verhaltensmerkmale werden in die Zielklasse generiert. Der Effekt dieser zunächst kompliziert erscheinenden Technologie ist der, dass der Quellcode für ein System faktisch aus fertigen Bausteinen zusammengesetzt werden kann. Der Anwendungsentwickler arbeitet konstruktiv auf Modellebene. Der Codegenerator erstellt den benötigten Quelltext. Wir kommen damit einer wirklich grafischen Programmierung sehr nahe. Als Voraussetzung sind zum Projektstart die Bibliotheken für das ARM Framework in das Modell importiert worden. Die angesprochenen Templates befinden sich im Paket **Tempos** und deren Unterordnern. //Tempos// kann mit konventionellen Klassen durchaus gemischt eingesetzt werden. Wir konzentrieren uns am Anfang aber darauf, nur mit //Tempos// zu arbeiten. Die gewünschten Tempos-Pakete finden Sie über dem Navigator (rechte Maustaste/UML-Pakete). Navigieren Sie sich zu den Paketen //temposArm_BasicIO//. Ziehen Sie das Template //Led// aus dem Navigator in das Diagramm. Verbinden Sie dieses mit der soeben angelegten Klasse LED in Form einer Realisierung (das passiert automatisch beim Verbinden). Danach legen wir auf die gleiche Art und Weise fest, an welchem Pin die LED angeschlossen ist. Wir wählen aus dem Ordner //temposARM_PinList// das Template //PinD13// und verbinden dies ebenso mit der Led. >{{uml:realisierungLED.JPG?375|}}{{uml:ledAnD13.JPG?350|}} Das ist aufwändiger beschrieben, als es zu tun. ;-) ===== Sequenzen ===== Sequenzdiagramme dokumentieren ausgewählte Verhaltensweisen eines Systems. In SiSy werden Sequenzdiagramme für jede Operation automatisch generiert, wenn diese ausgewählt ist. Der Entwickler kann anhand des dynamisch erzeugten Sequenzdiagrammes visuell überprüfen, ob die gewünschte Steuerstruktur von ihm erstellt wurde. Dieser Perspektivwechsel zwischen Quellcodezeilen und grafischer Darstellung der Codesequenz kann enorm dazu beitragen, die Qualität des Codes zu verbessern. Wählen Sie die Operation //onStart// in der Klasse //Controller// aus. Notieren Sie folgende kurze Codesequenz im Quelltexteditor. Verfolgen Sie dabei das Fenster mit dem Sequenzdiagramm. >>><code cpp> uint8_t wert; wert=led.getBlinkCode(); wert++; led.setBlinkCode(wert); </code> Das entsprechende Sequenzdiagramm sieht dann so aus: >>>{{uml:seqonstart.jpg|}} ===== Polymorphie ===== Polymorphie ist für Einsteiger oft ein recht schwer verständliches Thema. Dabei ist das Schwierige eher die Erstellung der polymorphen Basisklassen nicht jedoch die Anwendung der Polymorphie durch den Entwickler. Überlassen wir das Bauen der Klassenbibliothek mit Templates und polymorphen Basisklassen den Profis und wenden die uns vorliegenden Bibliotheken einfach an. Der Blinkcode unserer LED soll per Tastendruck weitergeschaltet werden. Dazu legen wir eine Klasse Taster an und wenden auf diese das Template //ButtonClickAndHold// aus dem Paket //temposArm_BasicIO// an. Der Taster ist am //PinA00// angeschlossen. Das entsprechende Template findet sich wiederum im Ordner //temposArm_PinList//. Immer dann, wenn wir virtuelle Operationen einer Basisklasse überschreiben, also eine eigene Operation mit gleicher Signatur erstellen, geben wir unserer Klasse ein individuelles Verhalten auf eine übergeordnete, allen Mitgliedern der Basisklasse gemeinsamen Nachricht. Nichts anderes ist Polymorphie. >{{uml:polytaste1.jpg?345|}} {{uml:polytaste2.jpg?345|}} >{{uml:polytaste3.jpg?700|}} Ziehen Sie aus der Objektbibliothek eine Operation auf die Klasse //Taster//. Das Werkzeug SiSy erkennt jetzt, dass virtuelle Operationen vorhanden sind, welche sich überschreiben lassen und bietet diese im //MethodenWizard// zur Auswahl an. Wir überschreiben die Operation //onClick// und erstellen somit einen eigenen Ereignisbehandler auf das Click-Ereignis des //ButtonClickAndHold//. Dort notieren wir den Quelltext zum Weiterschalten des Blinkcodes: >>><code cpp> app.led.nextBlinkCode(); </code> ===== Assoziation ===== Den Umstand, das der Taster sich direkt um die LED kümmert, können wir im Klassendiagramm mit einer //Assoziation// dokumentieren. Dazu ziehen wir eine Verbindung vom Typ //gerichtete Assoziation// vom //Taster// zur //Led// und beschriften diese. >>>{{:tasterasso.jpg?nolink&700|}} ===== Zustände ===== Eine kleine Erweiterung sollte unser Projekt noch erfahren. Ein //ButtonClickAndHold// verfügt für die Nutzerinteraktion nicht nur über das Click-Ereignis, sondern auch über Ereignisse, die ein langes Halten des Tasters durch den Nutzer signalisieren. Wir wollen das lange Halten des Tasters nutzen, um die LED wieder auszuschalten. Ziehen Sie dazu eine Operation auf die Klasse Taster und überschreiben Sie die Operation //onHoldStart//. Ergänzen Sie den Quellcode dieser Operation mit dem Befehl //app.led.off();// . Unsere kleine Anwendung erfüllt jetzt folgende Anforderungen: * nach dem Einschalten zeigt die LED den Blinkcode 1 an * bei jedem Click auf den Taster wird der Blinkcode um eins erhöht * durch langes Halten des Tasters wird die LED ausgeschaltet Bei dieser Gelegenheit schauen wir uns an, wie die Entwickler dieses schon nicht mehr ganz triviale Verhalten des Tasters konstruiert haben. Selektieren Sie den Baustein //ButtonClickAndHold//. Wählen Sie im Kontextmenü (rechte Maustaste) //Quelldiagramm öffnen//. >>>{{uml:gehezursm.jpg?700|}} Dort finden Sie das Attribut //state//. Aus diesem können Sie im Kontextmenü //nach unten// wählen und gelangen zum Zustandsdiagramm für Button. >>>{{uml:smbutton.jpg?700|}} Die Modellierung von Zustandsdiagrammen wird im myAVR Lehrbuch [[http://shop.myavr.de/Literatur/Software%20Engineering%20f%C3%BCr%20Embedded%20Systems.htm?sp=article.sp.php&artID=200045|Software Engineering für Embedded Systems]] näher beschrieben. SiSy ist in der Lage, aus solchen Zustandsdiagrammen den kompletten Quellcode, der die Zustandsmaschine realisiert, zu generieren und in die zugehörige Klasse einzubetten. Das Ergebnis ist in diesem Fall ein Taster, der zuverlässig entprellt ist und die Fähigkeit hat, auf Click und Halten unterschiedlich zu reagieren. ====== Videozusammenfassung ====== Mit dem soeben Erlernten sollten sich die im [[Framework|ARM C++ Framework]] präsentierten UML Diagramme jetzt lesen und nachvollziehen lassen. Und hier diesen Abschnitt wiederum als Videozusammenfassung. >>><flashplayer width="600" height="450" position="0">file=http://youtu.be/KNim1z3Fy_A</flashplayer> >>>[[http://youtu.be/KNim1z3Fy_A|besser auf youTube]] ====== Seminarhinweise ====== * [[http://shop.myavr.de/?sp=schulungen.sp.php|Seminarangebote]] ====== Nächstes Thema ====== * [[Hardware]] oder wenn Sie bereits beim Abschnitt C++ sind dann weiter mit * [[Grundsruktur einer einfachen Anwendung mit GrafikDisplay|Erste Schritte ohne µGL -> Die Grundstruktur einer objektorientierten Anwendung anlegen üben]] * [[Hallo Welt ohne µGL|oder Sie fühlen sich beim Anlegen eines UML-Projektes bereits sicher, weiter zu "Hallo Welt"]] * [[start|zurück zur Übersicht]]

grafische_programmierung_mit_der_uml.txt · Zuletzt geändert: 2025/03/10 17:05 (Externe Bearbeitung)