• Keine Ergebnisse gefunden

Fachhochschule für Technik Esslingen Studiengang Wirtschaftsinformatik

N/A
N/A
Protected

Academic year: 2022

Aktie "Fachhochschule für Technik Esslingen Studiengang Wirtschaftsinformatik"

Copied!
89
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

Fachhochschule für Technik Esslingen Studiengang Wirtschaftsinformatik

Fortgeschrittene Konzepte

Dr. rer. nat. Andreas Rau

http://www.hs-esslingen.de/~rau andreas.rau@hs-esslingen.de

(2)

In diesem Teil der Vorlesung sollen am Beispiel der GUI Programmierung weitergehende und allgemeine Konzepte vermittelt werden. Dazu gehört u.a. das Lokalitätsprinzip und die Einführung von inneren Klassen, die praktische Anwendung des Konzepts von Model- View-Controller sowie eine Reihe von Anwendungsmustern (Design-Patterns).

Inhalt

(3)

Grundprinzipien

Oberfläche ist aus Elementen zusammengesetzt (Komposition)

Benutzerinteraktion wird durch Basiselemente abgewickelt (Delegation) Hauptprogramm wird "zurückgerufen" wenn etwas passiert (Observer)

Bei der Einführung in Informatik 1 wurden verschiedene Klassen und Interfaces für das Hauptfenster (JFrame) und die Events/Listener, z.B.

von Buttons, eingeführt (ActionEvent/ActionListener). Allerdings trat als Empfänger der Events stets die Fensterklasse auf. Dies kann bei komplexeren Oberflächen verschiedene Probleme mit sich bringen:

Mangelnde Verständlichkeit (Button und Aktion voneinander entfernt) Mangelnde Übersicht (viele Aktionen in einer Listener-Methode)

Schlechte Wiederverwendbarkeit (alles oder nichts)

Behinderung der Arbeitsteilung (alles in einer gemeinsamen Methode)

Die Beachtung von Grundprinzpien der Programmierung kann hier helfen

(4)

Hintergrund des Lokalitätsprinzips ist es, zusammengehörige Dinge im Programm möglichst nahe beieinander zu beschreiben und dabei gleichzeitig globale Abhängigkeiten (insbesondere Ursache-Wirkung) und Auswirkungen zu minimieren (wie in der realen Welt). Dadurch wird u.a.

das Verständnis erleichtert (man kann Programmteile isoliert lesen und verstehen) sowie die Wartbarkeit verbessert (lokale Änderungen haben keine globalen Nebenwirkungen).

Beispiel:

lokale Variablen Schleifenvariablen

Mit dem Lokalitätsprinzip eng verknüpft ist das Prinzip "One thing at a time", d.h. die Beschränkung von Methoden bzw. Klassen auf eine klar definierte Aufgabe.

Lokalitätsprinzip und "One thing at a time"

(5)

Angewandt auf die GUI Programmierung folgt aus diesen Prinzipien der Wunsch (a) für jeden Button einen eigenen Listener zu definieren und (b) dies möglichst nahe bei der Definition des Buttons zu tun.

Prinzipiell wäre es möglich, das Ziel (a) zu erreichen, indem man für jeden Button eine eigene Listener-Klasse implementiert. Dies wäre jedoch nicht nur umständlich sondern würde auch das Ziel (b) unberücksichtigt lassen (1 File pro Klasse) sowie zu einer unübersichtlichen Vielfalt von Klassen führen.

Um diesen Widerspruch aufzulösen wurden die Inner Classes eingeführt.

Sie gestatten die einfache Definition von eigenständigen Hilfsklassen aus einer bestehenden Klasse heraus, wobei der Zusammenhang zwischen der Hauptklasse und der Hilfsklasse stets klar ist.

Hinweis: Inner Classes sind „syntaktischer Zucker“, d.h. eine bequeme

Schreibweise für etwas, was man auch so hätte realisieren können.

(6)

Inner Classes(2) – Anwendungsbeispiel/vorher

public class ButtonsBuiltIn extends JFrame implements ActionListener { JButton button1, button2, button3; // buttons as instance variables

public ButtonsBuiltIn() { super( "BuiltIn");

setDefaultCloseOperation( EXIT_ON_CLOSE);

getContentPane().setLayout( new GridLayout( 1, 3));

button1 = new JButton( "La");

button1.addActionListener( this);

getContentPane().add( button1);

// same for button2 and button3...

pack();

}

public void actionPerformed( ActionEvent e) { if (e.getSource() == button1) {

System.out.println( "La");

}

// same for button2 and button3...

}

public static void main( String[] args) { new ButtonsBuiltIn().show();

}

(7)

public ButtonsInner() { super( "Inner");

setDefaultCloseOperation( EXIT_ON_CLOSE);

getContentPane().setLayout( new GridLayout( 1, 3));

JButton button1 = new JButton( "La"); // button as local variable button1.addActionListener( new ActionListener() {

public void actionPerformed( ActionEvent e) { System.out.println( "La");

} });

getContentPane().add( button1);

// same for button2 and button3...

pack();

}

public static void main(String[] args) { new ButtonsInner().show();

} }

kürzer und kompakter!

(8)

Der new-Aufruf im Beispiel erzeugt an Ort und Stelle eine neue anonyme Klasse sowie eine Instanz derselben. Als Platzhalter für den Klassenname kann entweder (a) der Name einer Klasse oder (b) der Name eines Interfaces verwendet werden. Dies bewirkt folgendes:

(a) die neue Klasse erbt von der angegebenen Klasse

(b) die neue Klasse erbt von Object und implementiert das Interface

Die neue Klasse kann also nicht gleichzeitig erben und implementieren!

Hinter dem new-Aufruf kann ein Klassenrumpf angegeben werden, um Methoden zu definieren und/oder zu überschreiben. Der Name der neuen Klasse wird vom Compiler automatisch generiert (vgl. class-File).

Folgen der Anonymität:

Die neue Klasse kann keinen Konstruktor mit Parametern* haben Die neue Klasse kann keine öffentlichen spezielle Methoden haben Es ist nicht möglich mehr als eine Instanz zu erzeugen

Es gibt keine Variablen mit dem Typ der Klasse(!)

Inner Classes(4) – Eine erste Erklärung

(9)

Neben der gezeigten Verwendung in GUIs gibt es noch viele weitere Fälle, in denen Inner Classes nützlich sind. Mit Inner Classes kann mann Klassen, die zusammenarbeiten, noch enger zusammen bringen und

dabei durch die Verschachtelung deren Hierarchie klar machen wobei der Namensraum des Pakets nicht "verschmutzt" wird. Vielmehr

agiert dabei die umgebende Klasse als neue Art von Namensraum.

Beispiele:

Daten oder Iteratoren in Collections

Außerdem kann man mit Inner Classes als Objekt verpackte Funktionen dynamisch an der Stelle erzeugen, an der sie benötigt werden.

Beispiele:

Comparatoren, Runnables (kommt später) oder ... ActionListener

(10)

Insgesamt existieren 4 Varianten von Inner Classes: Neben den gezeigten und vorrangig bei der GUI Programmierung eingesetzten anonymen Klassen (anonymous classes) gibt es noch geschachtelte Klassen (nested classes), Mitgliedsklassen (member classes) und lokale Klassen (local classes). Diese unterscheiden sich insbesondere bzgl. ihres Verhältnisses zur äußeren Klasse.

Bei genauer Betrachtung zeigt sich, dass die Eigenschaften dieser Varianten aufeinander aufbauen und dabei Schritt für Schritt den Weg von einer unabhängigen "Schwesterklasse" im gleichen Paket oder gar File zu einer immer stärker integrierten "Symbiontenklasse*" vollziehen. Dabei erwirbt die Inner Class Zug um Zug nahezu alle Eigenschaften und Rechte der bekannten Klassenelemente wie Klassen- und Instanzmethoden sowie den Zugriff auf Klassen-, Instanz- und lokale Variablen.

* Der Begriff "Tochterklasse" wird bereits für die Vererbung verwendet.

Inner Classes(5) - Variantenübersicht

(11)

Zielsetzung:

Zusammenfassung von Klassen und deren Hilfsklassen ohne den globalen Namensraum zu "verschmutzen". Die äußere Klasse funktioniert dabei analog zu einem Paket, d.h. der voll qualifizierte Klassename der inneren Klasse ist

"package(s).OuterClass.NestedClass". Die Objekterzeugung erfolgt ganz normal über den new-Operator. Nested Classes sind im Grund ganz normale Klassen, die in den Gültigkeitsbereich einer anderen Klasse eingebettet sind und dadurch besondere Zugriffsrechte auf private Elemente haben.

Wirkung:

Integration auf Klassenebene, Zugriff auf Klasse direkt (auf Instanz indirekt) Anwendungsbeispiel:

public class MyMap {

public static class KeyValuePair { Object key, value;

}

List data = ArrayList<KeyValuePair>() // ...

}

(12)

Zielsetzung:

Zusammenfassung von Klassen mit Hilfsklassen; enge Zusammenarbeit zwischen beiden Klassen. Eine Instanz einer Member Class gehört stets zu einer Instanz der äußeren Klasse und ist mit besonderen Zugriffsrechten auf diese Instanz ausgestattet. Realisiert ist dies durch einen unsichtbaren Verweis auf die Instanz der äußeren Klasse auf die in der inneren Klasse mittels

"OuterClass.this" zugegriffen werden kann. Um diesen Verweis zu initialisieren, müssen Instanzen der inneren Klasse stets mittels

"OuterObject.new MemberClass()" für eine Instanz der äußeren Klasse erzeugt werden.

Wirkung:

Integration auf Instanzebene, direkter Zugriff auf Klasse und „Vaterinstanz“

Anwendungsbeispiel:

public class MyCollection {

public class MyIterator implements Iterator { // kann auf MyCollection zugreifen

}

Inner Classes(7) – Member Classes

(13)

Jede Objektmethode bekommt die Pseudovariable this als unsichtbaren Parameter übergeben. Der Aufruf

obj.setFoo( value)

kann also interpretiert werden als

setFoo( obj, value) // wobei this = obj

In gleicher Art und Weise bekommt der Konstruktor einer Member Class eine unsichtbare Referenz der äußeren Klasse übergeben, d.h. der Aufruf

obj.new MemberClass() kann interpretiert werden als new MemberClass( obj)

Man spart sich also den manuellen Verdrahtungsaufwand (s. Beispiel n. Folie) Daraus folgt: ohne Objektreferenz geht es nicht, da sonst ein Parameter fehlen würde. Jedoch kann man ein vorangestelltes this immer weglassen (=Default).

(14)

public class Outer {

int id;

public static class NestedInner { Outer o;

public NestedInner( Outer o) { this.o = o;

}

public void test() {

System.out.println( o.id);

} }

public class InnerMember { public void test() {

System.out.println( id); // Outer.this.id }

}

public static void main( String[] args) { Outer o = new Outer();

new NestedInner( o).test();

o.new InnerMember().test();

}

Inner Classes(7b) – Member Classes

(15)

Engstmögliche Kapselung von Hilfsklassen in einer Methode. Typischerweise nur wenige Instanzen (aber mehr als eine). Da die Sichtbarkeit von lokalen Klassen ohnehin auf die umgebende Methode beschränkt ist, ist die Angabe des Zugriffsmodifizierers bei der Klassendeklaration überflüssig.

Wirkung:

Integration in einer Methode, Zugriff auf Klasse+“Vaterinstanz“+lokale Variablen Anwendungsbeispiel:

public class OuterClass { public void someMethod() { class LocalClass {

// body...

}

LocalClass instance = new LocalClass();

// use it...

} }

(16)

Hintergrund:

Instanzen von Local Classes erhalten genau wie Instanzen von Member Classes eine unsichtbare Referenz auf das umgebende Objekt. Wie wird jedoch der Zugriff auf die lokalen Variablen realisiert?

Das Problem ist einerseits, dass lokale Variablen nicht wie Instanzvariablen über eine Referenz greifbar sind, und anderseits, dass ein lokales Objekt noch weiterexistieren kann, nachdem die erzeugende Methode längst schon beendet ist (d.h. die lokalen Variablen zerstört sind).

Der einzige Ausweg aus diesem Dilema besteht darin, die verwendeten lokalen Variablen ebenfalls in die Instanz hineinzukopieren! Um sich mit der dadurch keine neuen Probleme einzuhandeln müssen diese dazu final sein, damit sie sich Kopie und Original nicht auseinanderentwickeln können.

Inner Classes(8a) – Local Classes

(17)

Zielsetzung:

Namenlose "Wegwerfklassen" für einmalige Anwendung; maximale Bequemlichkeit bei der Deklaration, nur eine Instanz. Die Deklaration der Klasse und die Instantiierung der Instanz erfolgen in einem Schritt.

Wirkung:

Vereinfachung der Integration in einer Methode gegen Einschränkungen Deklarationsbeispiel:

public class OuterClass {

public void sortPersons( ArrayList persons) { Collections.sort( persons, new Comparator() { public int compare(Object o1, Object o2) {

return ((Person)o1).name.compareTo( ((Person)o2).name);

}

public boolean equals( Object o) { return false;

} });

}

(18)

Inner Classes(10a) – Verdrahtung

Frame this

sampleField

NestedClass this

frame

MemberClass this

Frame.this

(19)

Der Zugriff auf andere Bereiche ist nur von Innen nach Außen möglich!

Paket Datei

Klasse

Objekt

Instanzmethode

Block/Kontrollstruktur Klassenmethode

Block/Kontrollstruktur Nested Class

Member Class

Local/Anonymous Class* Local/Anonymous Class*

*Zugriff auf lokale Variablen nur wenn final

(20)

Inner Classes(11) – Vergleich

Sichtbarkeit nach Außen

Ja Viele

Ja Einige

Nein, da nur in Methode Ja Wenige

Genau Eine Klassenname,

Konstruktor, sowie zusätzl. Methoden

Statische Eigenschaften

Spezielle Zugriffsrechte

Anzahl Instanzen

Nested Class;

vollwertige Klasse Je nach Zugriffsmodifier Klassenmethoden

Klassenvariablen

Klassenvariablen da keine Referenz

Member Class;

gekoppelte Klasse Je nach Zugriffsmodifier Keine, da nicht

eigenständig

Klassenvariablen Instanzvariablen

Local Class Keine, da nicht

eigenständig

Klassenvariablen Instanz- und finale lokale

Variablen

Anonymous Class Nein, da kein Klassenname

Nein, da kein Klassenname

Keine, da nicht eigenständig

Klassenvariablen Instanz- und finale lokale

Variablen

(21)

JLabel, statische Anzeige von Text und Bildern(neu) JButton, Schaltfläche mit Text und Bildern(neu)

JTextField, Einfaches Eingabefeld Neue GUI Elemente

JCheckBox, unabhängige Ja/Nein Auswahl JRadioButton, abhängige Ja/Nein Auswahl JSlider, Schieberegler horizontal oder vertikal JList, Listenanzeige

Neue Hilfsklassen

Box, Vereinfachung für BoxLayout mit Struts und Glue

BorderFactory, Erzeugung von verschiedenen Umrandungen ImageIcon, Anzeige von Bildern auf Labels und Buttons

ButtonGroup, Verwaltung von RadioButtons

JScrollPane, Verpackung von großen Komponenten, z.B. Listen

Details siehe Vorlesungsbeispiele, Java Buch, Java Tutorial und API-Referenz

(22)

Bei der Implementierung von GUIs in Java spielt das Konzept des Layout- Managers eine wichtige Rolle. Im Unterschied zu den Container Klassen von Swing, die Components "einfach nur enthalten" ist ein LayoutManager für die räumliche Anordnung bzw. Ausrichtung vom Components zuständig.

Entscheidend dabei ist, dass diese Anordnung (1) nach einem bestimmten Prinzip und (2) dynamisch erfolgt. Dadurch erleichtern LayoutManager die Erzeugung von bestimmten Anordnungen und sorgen dafür, dass das zugrundeliegende Layout-Prinzip auch bei Änderungen, z.B. der Fenstergröße, erhalten bleibt und tragen so zu Produktivität, Wartbarkeit und Ergonomie bei.

Jeder LayoutManager basiert auf einem Prinzip (Absolute, Flow, Box, Grid, GridBag). Komplexe Layouts beinhalten jedoch mehrere Prinzipien. Zu ihrer Realisierung werden verschiedene Komponenten bzw. deren LayoutManager kombiniert und verschachtelt. Dies plant sinnvollerweise vorab mit einer Skizze.

Vorgehensweise:

Flacher Entwurf -> Hierarchische Zerlegung (Skizze) -> Implementierung LayoutManager Revisited

(23)

NullLayout

container.setLayout( null);

component.setBounds( x, y, width, height);

container.add( component);

Funktion manuelle absolute Positionierung

Hinweise Unflexibel, nur in Verbindung mit fester Fenstergröße sinnvoll FlowLayout

container.setLayout( new FlowLayout())

Funktion Komponenten "fließen" wie Wörter; Zeilen in sich zentriert Hinweise Praktisch z.B. für Toolbars

BoxLayout

container.setLayout( new BoxLayout( container, BoxLayout.Y_AXIS));

container.add( component);

Funktion Komponenten werden (wie in einer Kiste) horiz. oder vert. gestapelt Hinweise Für einfache Spalten/Zeilenstruktur

(24)

BorderLayout

container.setLayout( new BorderLayout());

container.add( component, BorderLayout.NORTH);

Funktion Einteilung des Containers in Zonen (NORTH..WEST, CENTER) Hinweise Als Top-Level Layout für Toolbar (NORTH), Statusbar (SOUTH), ...

GridLayout

container.setLayout( new GridLayout( rows, cols));

container.add( component); // Zeilenweise

Funktion Aufteilung des Containers als Gitter (alle Zellen gleich groß) Hinweise Für Tastenfelder oder Anzeigen (z.B. Taschenrechner)

GridBagLayout

container.setLayout( new GridBagLayout());

GridBagConstraints gbc = new GridBagConstraints();

// gbc konfigurieren mit anchor, fill, gridx/y, weightx/y, ...

container.add( component, gbc);

Funktion Aufteilung des Containers als flexibles Gitter

Hinweise Mächtigstes aber auch aufwendigstes Layout, z.B. für Formulare LayoutManager Zusammenfassung(2)

(25)

Weitere Layoutmanager (vgl. API Doku)

CardLayout, gestapelte Ebenen (nur eine sichtbar) OverlayLayout, gestapelte Ebenen (alle sichtbar)

GroupLayout, (vertikales und horizontales Raster, für GUI Builder)

Panels mit besonderen Layout-Eigenschaften

JScrollPane, scrollbares Panel (für Tabellen, Bäume, ...) JTabbedPane, Panel mit Karteireitern

JSplitPane, Panel mit flexibler horiz. oder vert. Aufteilung

Weitere Hinweise und Beispiele zu den einzelnen LayoutManagern finden sich Im Buch

In der API-Doku der jeweiligen Klasse Im Java Tutorial

(26)

Tipps zum Aufbau von GUIs

Die Wahrscheinlichkeit für Fehler steigt mit der Komplexität des Layouts.

Deswegen: Layout vorher auf Papier planen um für jeden Bereich das am Besten geeignete und einfachste Layout auswählen zu können.

Bei der Verschachtelung von Layouts wirken inneres und äußeres Layout zusammen. Dabei können neue Fehler entstehen. Um diese zu vermeiden bzw.

leichter finden zu können sind folgende Tipps hilfreich:

Layout Top-Down und Schritt für Schritt aufbauen Layouts/Panels nicht tiefer verschachteln als nötig

Für jeden Teilbereich möglichst einfaches Layout wählen

Zuerst Top-Level Layout mit Dummies (leeren Panels) testen

Dabei Panels einfärben um das Verhalten besser beobachten zu können Dann Teilbereiche als eigene Methoden realisieren und einbauen

Korrektheit jedes Teilbereichs zuerst in eigenem Fenster testen LayoutManager Zusammenfassung(4)

(27)

Man kann Panels und Komponenten in einem Layout mischen, nicht jede Komponente muss in ein einzelnes Panel. Als Instanzvariablen müssen nur solche Komponenten realisiert werden, auf die später zugegriffen werden muss.

Dies sind gilt normalerweise für Komponenten mit Inhalt/Zustand wie Eingabe- oder Auswahlfelder. Labels und Buttons kann man dagegen meist als lokale Variablen realisieren.

Das GridBagLayout ist am mächtigsten/flexibelsten aber auch am aufwendigsten und macht nur dann Sinn, wenn man (1) mehreren Komponenten hat und (2) diese in unterschiedlich großen Zellen unterbringen möchte oder (3) gezielt Einfluss auf das Verhalten beim Ändern der Fenstergröße nehmen möchte.

Hinweis zu GridBagConstraints:

Das Attribut fill funktioniert nur im Zusammenspiel mit den Attributen weightx

und weighty. Das Attribut fill regelt ob überschüssiger Platz auf die anderen Elemente verteilt wird, die Attribute weightx und weighty regeln wie der Platz auf die anderen Elemente verteilt wird. Der Standardwert ist weightx = weighty = 0, d.h. ohne explizite Zuweisung werden alle Elemente nicht angepasst.

(28)

Bei der Implementierung von GUIs hat sich das Prinzip der Aufteilung in Model (Datenspeicherung), View (Anzeige) und Controller (Eingabeverarbeitung) allgemein bewährt. In Java wird dieses Prinzip in der abgewandelten Form Model-ViewController angewandt.

Bei näherer Betrachtung der Klassenbibliothek fällt auf, dass sich dies bis auf die Komponentenebene durchzieht. So hat selbst jeder Button ein darunterliegendes AbstractButtonModel in dem bei ToggleButtons, CheckBoxes und RadioButtons der aktuelle Zustand gespeichert wird. Bei Controls wie Listen, Tabellen oder Bäumen ist die Notwendigkeit für ein Modell schon naheliegender.

Die Klassenbibliothek enthält Default-Modelle (Default<ControlTyp>Model), welche die normale Verwendung aller Controls abdecken. Diese sind oftmals von abstrakten Modellen (Abstract<ControlTyp>Model) abgeleitet. Daher muss man nur selten ein eigenes Modell völlig neu schreiben und nimmt somit die Trennung oftmals gar nicht wahr. Viele fortgeschrittene Möglichkeiten erschließen sich jedoch nur durch eigene Modelle. Dies gilt insbesondere für Tabellen und Bäume. Diese Controls benötigen fast immer ein eigenes Modell.

Model-View-Controller Revisited

(29)

Delegate

View Controller

Model

Applikationslogik

get set event

fire event get set

Benutzeranzeige Benutzereingaben Erläuterungen:

View und Controller bilden gemeinsam den Delegate. Dieser dient zur Interaktion des Benutzers mit dem damit verknüpften und als Referenz gespeicherten Modell.

Die View liest Daten über get-Methoden vom Modell, führt ggf. Updates mit Hilfe von set-Methoden durch und wird über events über Änderungen informiert

Der Rest der Applikation kann ebenfalls über get- und set-Methoden auf das Modell zugreifen. Dabei werden ggf.

Events durch das Modell ausgelöst (dies ist bei eigenen Modellen zu beachten, vgl. z.B. AbstractTableModel). In

Ausnahmefällen kann ein Event jedoch auch explizit ausgelöst werden.

(30)

Listboxes (JList) zur reinen Anzeige können einfach mit Arrays befüllt werden. Ein eigenes ListModel ist nur nötig, wenn die Liste dynamisch verändert werden soll.

Beim Einsatz von Tabellen (JTable) muss mit Ausnahme von trivialen Tabellen mit Standarddatentypen ein eines eigenen TableModel implementiert werden, um Zeilen und Spalten in der Tabelle auf Fachobjekte und deren Attribute abzubilden.

Auch bei Bäumen (JTree) ist Handarbeit notwendig, u.a. zum Aufbau der Baumstruktur samt Inhalt vor der Übergabe im Konstruktor. Dazu dienen in den meisten Fällen Instanzen der Klasse DefaultMutableTreeNode, in die beliebige eigene Objekte als Inhalt eingehängt werden können. Alternativ kann ein geschlossenes TreeModel ähnlich zu TableModel definiert werden. Eine Übergabe der Daten als geschlossenes Objekt (z.B. Array) wie bei Listen und Tabellen ist hier jedoch aufgrund der strukturellen Komplexität nicht möglich.

Die Objekte in Listboxen, Tabellen und Bäumen werden stets mit Hilfe der Methode toString() angezeigt. Diese muss also in geeigneter Weise implementiert werden.

Auch die anderen Controls wie Buttons (JButton) oder Textfelder (JTextField) arbeiten übrigends mit Modellen – nur sieht und braucht man sie nicht so oft!

Modelle für Listen, Tabellen und Bäume

(31)

definiert, z.B. ListModel. Zu diesen gibt es i.d.R. eine Default-Implementierung, z.B.

DefaultListModel, welche die meisten Anwendungsfälle abdeckt.

Allen Modellen gemein ist, dass die im zugehörigen Interface beschriebenen Methoden streng auf die Bedürfnisse des zugehörigen Controls zugeschnitten sind. Dies kann man an den Modellen für Listboxes, Tabellen und Bäume sehr schön erkennen:

Eine Listbox dient primär der Anzeige, deshalb enthält das zugehörige Modell nur die lesenden Methoden getSize() und getElementAt(). Deswegen kann man über das Listbox-interne "Not-Modell" auch keine Elemente hinzufügen oder löschen!

Tabellen können editiert werden, deshalb umfasst ihr Modell neben lesenden auch schreibende Methoden und auch die Strukturbeschreibung erfordert mehrere Methoden:

getColumnNames(), getColumnCount(), getRowCount(), getValueAt(), setValueAt(), isColumnEditable().

Bei Bäumen ist das Modell verrückterweise wieder einfacher, da Bäume nicht editiert werden können und eine einfache rekursive Struktur haben: getRoot(), getChildCount(), getChild() sowie getIndexOfChild() und isLeaf().

(32)

Modelle für Listen, Tabellen und Bäume

Liste Tabelle Baum

Verwendungszweck Anpassbarkeit

Basisfunktionalität nur Anzeige/Selektion Anzeige/Selektion, Pflege Anzeige/Selektion Erweiterte Funktionalität keine

Struktur

Zugriff Index Zeile, Spalte Pfadangabe

GUI Element

Basisklasse Implementierung

Auflistung von Objekten, z.B. über ihren Namen

Auflistung von Objekten inkl. ihrer Eigenschaften

Darstellung hierarchisch strukturierter Objekte

Renderer Renderer, Editor Renderer, Editor

Spalten vertauschen Sortieren, Filtern

Knoten auf-/zuklappen Eindimensionale Liste von

Objekten

Zweidimensionale Matrix aus Zeilen und Spalten.

Dabei entsprechen Zeilen verschiedenen Objekten und die Spalten deren Eigenschaften (mit einem gleichartigen Datentyp)

Rekursive Struktur von Objekten in Vater-Kind Beziehung. Ein Vater kann mehrere Kinder haben, jedes Kind hat genau einen Vater.

JList JTable JTree

Modelschnittstelle ListModel TableModel TreeModel

TreeNode AbstractListModel AbstractTreeModel

DefaultListModel DefaultTreeModel DefaultTreeModel,

DefaultMutableTreeNode

Gegenüberstellung von Liste, Tabelle und Baum

(33)

zu beeinflussen werden sog. Renderer und Editoren verwendet. Dies sind im Grunde gewöhnliche Oberflächenelemente die bei der Tabelle bzw. dem Baum registriert oder aus überladenen Methoden zurückgeliefert werden. Diese werden jedoch nicht für jedes Element einzeln instanziiert. Stattdessen dient ein einziger Renderer als "Stempel" zur sukzessiven Darstellung aller Elemente. Der Editor wird an der jeweils ausgewählten Stelle eingeblendet.

Damit ergibt sich folgende Struktur

Für Tabellen existiert ein Default Renderer und ein Default Editor die intern abhängig vom Datentyp einer Zelle verschiedene Komponenten verwenden. Der Default Renderer verwendet normalerweise ein Label und der Default Editor ein Textfield. Mit diesen Komponenten können Zellen mit Strings dargestellt bzw.

editiert werden. Für boolsche Werte wird dagegen eine Checkbox zur Anzeige und Editierung verwendet. Bei Bäumen besteht der Default Renderer neben einem Label zusätzlich aus einem Icon.

View

Renderer

Editor Model

(34)

Entwurfsmuster (Patterns) dienen wie Bibliotheken (Libraries) der Wiederverwendung und damit der Erhöhung der Produktivität und der Vermeidung von Fehlern. Im Unterschied zu Bibliotheken können Entwurfsmuster jedoch nicht durch direkten Einbau wiederverwendet werden.

Fasst man eine Bibliothek als Sammlung von Bausteinen(=Lösungen) auf, so entspricht ein Entwurfsmuster einer Bauanleitung(=Lösungsweg) die für individuelle Probleme einer bestimmten Klasse verwendet und angepasst werden kann. Anpassungen, die nur die Typisierung betreffen, lassen sich in anderen Programmiersprachen durch die Unterstützung von Schablonen (Templates) automatisieren. Dies gibt es in Java erst seit Version 5.

Durch die Notwendigkeit zur immer neuen Implementierung scheinen Entwurfsmuster zunächst gegenüber Bibliotheken im Nachteil zu sein.

Tatsächlich kann die damit verbundene Flexibilität aber auch ein Vorteil sein. Die größte Stärke von Entwurfsmustern liegt jedoch in ihrer Anwendbarkeit für Architekturen. Bibliotheken sind dagegen meist auf Funktionen beschränkt und reichen nur als Frameworks in den Bereich der Architekturen hinein.

Entwurfsmuster (Patterns) und Schablonen (Templates)

(35)

Stellen eingesetzt. Grundidee dieses Patterns ist die Kommunikation zwischen zwei Objekten, deren Klassen sich nicht gegenseitig kennen (sollen).

Dabei tritt das eine Objekt als Datenquelle und das andere als Beobachter derselben auf. Die Abhängigkeit geht daher stets vom Beobachter zur Datenquelle, nicht umgekehrt. Viele Beobachter können an derselben Datenquelle interessiert sein.

Die Implementierung dieses Patterns in Java basiert stets auf einem Interface:

Das Interface definiert Methoden zur Informationsübertragung Der Beobachter implementiert dieses Interface

Die Datenquelle ruft diese Methoden auf

Die Kontaktaufnahme erfolgt über eine Methode zur Registrierung von Beobachtern bei der Datenquelle. Dabei sieht die Datenquelle von jedem Beobachter nur das Interface, muss die wahre Klasse also nicht kennen. Zum Transport der Information wird oftmals ein eigenes (Event-)Objekt übergeben.

Dies ist jedoch nicht zwingend notwendig.

(36)

Obwohl in Java beide vorrangig für GUIs eingesetzt werden basieren sowohl Modelle als auch Events auf allgemeinen Prinzipien:

Modell: "Ich verwalte Daten und kommuniziere deren Veränderungen"

Event: "Ich repräsentiere eine Information über ein Ereignis"

Man kann diese Prinzipien z.B. im Rahmen des Observer-Patterns auch für andere Dinge äußerst sinnvoll einsetzen. Diesem Umstand wurde z.B. in der Programmiersprache Smalltalk dadurch Rechnung getragen, dass es direkt unter Object eine allgemeine Klasse mit dem Namen Model gab. In Java gibt es stattdessen eine Klasse Observable sowie ein Interface Observer.

Das Beispiel zum Model-View-Controller in Informatik 1 war also ein Sonderfall:

Im Allgemeinen kann man in Java sehr leicht verschiedene GUIs auf dasselbe Model aufsetzen. Die Umstellung einer textbasierten Anwendung erfordert aber i.d.R. die Anpassung fundamentaler Datenstrukturen auf entsprechende Modelle. So ist es z.B. nicht möglich, ein gewöhnliches Array dynamisch mit einer Listbox zu verbinden. Dafür ist ein DefaultListModel nötig.

Beobachtungen

(37)

existieren verschiedene Java Umgebungen:

J2SE (Standard Edition), für Desktop Anwendungen

J2EE (Enterprise Edition), für Webapplikationen mit Datenbanken J2ME (Micro Edition), für mobile Geräte wie PDAs und Handys

Diese unterscheiden sich im wesentlichen von der mitgelieferten Laufzeitumgebung bzw. Klassenbibliothek wobei J2EE eine Erweiterung und J2ME ein Subset von J2ME ist.

Während die virtuelle Maschine stets hauptsächlich der Abstraktion von der Hardware realisiert dient bei J2ME die Klassenbibliothek explizit zur Entkopplung der Applikation von dem jeweiligen Betriebssystem. Dazu sind im Rahmen des MIDP (Mobile Information Device Profile) verschiedene Basisdienste standardisiert, die von den unterschiedlichen Geräteherstellern implementiert werden müssen. Zusammen mit CLDC unterstützt MIDP sowohl die Realisierung von Oberflächen als auch gerätespezifische Dienste wie den Versand von SMS.

(38)

Die Entwicklung von J2ME Applikationen kann entweder mit dem Wireless Toolkit (WTK) von Sun oder einem Plugin für eine Entwicklungsumgebung, z.B.

Netbeans erfolgen. Beide beinhalten neue Compiler, Debugger sowie ein Emulationswerkzeug mit dem die Applikation auf dem PC getestet werden kann.

Aufgrund der beschränkten Rechenleistung und Resourcen mobiler Geräte werden darin Schritte wie die Codeprüfung in den Compilationsschritt verlagert und der Umfang der Laufzeitumgebung ist stark eingeschränkt. So stehen z.B.

keine floating-Point Operationen zur Verfügung.

In Anlehnung an Applets werden J2ME Applikation als Midlets bezeichnet. Sie werden in als .jar-File verteilt und durch ein separates .jad-File beschrieben.

Sinn des .jad-Files ist die kompakte Beschreibung der Applikation als Entscheidungsgrundlage für einen möglicherweise kostspieligen Download.

Beide Files zusammen bilden eine sog. Midlet Suite (siehe Vorlesungsbeispiel).

Details zur J2ME Entwicklung siehe die Dokumentation zum WTK und MIDP.

Exkurs: J2ME und MIDP(2)

(39)

Desktop Applet Midlet Startmethode main() init()/start() startApp()

Hauptklasse JFrame JApplet Midlet

Container Container Container Screen

JPanel JPanel Form

... ... Canvas

Komponenten JComponent JComponent Item

JLabel JLabel StringItem

JTextfield JTextfield TextField

JRadioButton JRadioButton ChoiceGroup

... ... ...

(40)

Gegenüberstellung von Oberflächen: Desktop/Applets/Midlets

Desktop Applet Midlet

Interaktion JButton JButton Command

Observer ActionListener ActionListener CommandListener

[am Button] [am Button] [am Screen]

Zeichnen paint(Graphics) paint(Graphics) paint(Graphics)

Sonstiges:

Bei Desktop und Applet volle Kontrolle über Inhalte sowie Layout/Menüstruktur Bei Midlet nur Inhalte und automatische Umsetzung gemäß Plattformphilosophie

Exkurs: J2ME und MIDP(4)

(41)

mehrerer Programme. Diese wird erreicht, indem die Rechenzeit des Prozessors zeitlich unter diesen Programmen aufgeteilt wird. Die Programme (inkl. dem Betriebssystem selbst) wechseln sich so schnell ab, das der Eindruck der Gleichzeitigkeit entsteht. Für echt gleichzeitige Ausführung mehrerer Programme sind mehrere Prozessoren erforderlich. Weitere Voraussetzung für die gleichzeitige Ausführung mehrerer Programme ist deren Unabhängigkeit.

Parallele ablaufende Programme werden vom Betriebssystem als sog. Prozesse verwaltet. Zur Aufteilung der Rechenleistung gibt es verschiedenen Verfahren:

Batch Betrieb (keine Aufteilung)

Timesharing (feste Zeitscheiben)

Prioritätsgesteuert (wichtige Programme zuerst/öfter)

Kooperation (Programme geben Prozessor freiwillig ab)

Scheduling Verfahren, bei denen einem Programm der Prozessor "unfreiwillig"

entzogen wird, nennt man preemptive.

(42)

Beim Wechsel zwischen zwei Prozessen (Kontextwechsel, engl. context switch) muss deren Zustand gesichert bzw. wiederhergestellt werden. Der dazu notwendige Aufwand führt zu "Reibungsverlusten" die umso größer sind, (1) desto größer der Kontext ist und (2) je öfter gewechselt wird.

Bei Betriebssystemprozessen umfasst der Kontext

Stack

Programmzähler

Inhalt der Prozessorregister

Allokierte Betriebsmittel

Arbeitsspeicher des Prozesses

Programmcode des Prozesses

Das ist eine ganz Menge, weswegen Betriebssystemprozesse auch als schwergewichtige Prozesse bezeichnet werden.

Threads – Hintergrund

(43)

Programms möglich. Dabei laufen mehrere Kontrollflüsse (Threads) quasi- parallel ab, teilen sich jedoch den Programmcode und den Arbeitsspeicher. Sie werden daher als leichtgewichtige Prozesse bezeichnet. Auch Java bietet Threads an und bildet Sie wenn möglich auf Betriebssystemthreads ab.

Die gemeinsamen Ressourcen der Threads erleichtern die Umschaltung, können jedoch auch zu Konflikten führen. Daher sind ggf. besondere Synchronisationsmaßnamen notwendig.

Prozess 1 Prozess 2 Prozess 3

Stack Stack Stack Stack

Programmzähler Programmzähler Programmzähler Programmzähler Prozessorregister Prozessorregister Prozessorregister Prozessorregister

Betriebsmittel Betriebsmittel Betriebsmittel Arbeitsspeicher Arbeitsspeicher Arbeitsspeicher

Programmcode Programmcode Programmcode

Thread 1 Thread 2

heavyweight heavyweight lightweight

(44)

Threads – Hintergrund

Zustandsübergänge Ähnlich für Prozesse und Threads.

Sowohl vorhandene als auch nicht vorhandene Übergänge sind für das Verständnis

interessant!

kill ready-to-run

running blocked

dead unknown

schedule preempt

block release

finish/kill kill

create

(45)

gesteuert. Zustandsübergänge werden durch die virtuelle Maschine gesteuert und können über Methodenaufrufe und spezielle Ereignisse beeinflußt werden.

Erzeugung

Möglichkeit 1: Eigene Klasse von Thread ableiten und run() überschreiben

Möglichkeit 2: Thread-Objekt erzeugen und Runnable Objekt übergeben Starten

Aufruf von t.start()

Unterbrechen/Pausieren

Aufruf von t.yield()

Aufruf von t.sleep() // kann InterruptedException werfen, siehe unten Abbrechen/Beenden

Aufruf von t.interrupt() // wirft InterruptedException „in den Thread“

Ablauf der run()-Methode

(46)

public class ThreadExample extends Thread { public void run() {

for (int i=0; i<10; ++i) { System.out.print( "T");

yield();

} }

public static void main(String[] args) { Thread t1 = new ThreadExample();

Thread t2 = new Thread( new Runnable() { public void run() {

for (int i=0; i<10; ++i) { System.out.print( "R");

yield();

} } });

t1.start();

t2.start();

for (int i=0; i<10; ++i) { System.out.print( "M");

yield();

} } }

Threads – Beispiel 1

(47)

public static void main(String[] args) { for (int i=0; i<10; ++i) {

System.out.print( "M");

try {

Thread.sleep(1000);

} catch (InterruptedException ex) { }

} } }

(48)

In Programmen mit einer graphischen Oberfläche existiert ein separater Thread für GUI Aktualisierung und GUI Events (z.B. Listener). Dieser arbeitet in einer Endlosschleife alle GUI Ereignisse ab und darf nicht für andere Aufgaben genutzt werden, da sonst u.U. die Benutzeroberfläche (zeitweise) blockiert wird!

Im Umkehrschluss sollte kein normaler Thread direkt auf die Benutzeroberfläche zugreifen (Ausnahmen sind nur vor der Anzeige der Oberfläche möglich!).

Kopplung/Entkopplung

GUI->Non: Neuer Thread, z.B. SwingWorker Non->GUI: SwingUtilities.invokeLater()

Das Java Tutorial enthält weitere interessante Hinweise zum Thema!!!

Threads – GUI Thread

AnyThread AnyThread

AnyThread Event-Queue GuiThread

GUI

Events einstellen

z.B. repaint() Events abarbeiten

GUI bearbeiten

(49)

import javax.swing.*;

public class EventThreadDemo extends JFrame { public EventThreadDemo() {

JButton button = new JButton( "Klick!");

button.addActionListener( new ActionListener() { public void actionPerformed( ActionEvent e) { Thread worker = new Thread( new Runnable() { public void run() {

// do something that takes long }

});

worker.start();

} });

getContentPane().add( button);

pack();

}

public static void main(String[] args) {

SwingUtilities.invokeLater( new Runnable() { public void run() {

new EventThreadDemo().setVisible( true);

} });

}

(50)

Zweck der Synchronisation

Ablaufsteuerung / Koordination von Threads

Schutz kritischer Abschnitte (Bearbeitung gemeinsamer Daten) Instrumente zur Ablaufsteuerung

sleep() - festgelegte Zeit abwarten

wait()/notify() - einschläfern und aufwecken von Threads join() - Rendezvouz (Staffelholz-Übergabe)

yield() - Prozessor freiwillig abgeben

Instrumente zum Schutz kritischer Abschnitte ( z.B. ConcurrentModification) synchronized Abschnitt (mit beliebigem Monitorobjekt)

synchronized Methoden (für aktuelles Objekt)

gemeinsam benutzte Attribute (neuer Modifier "volatile")

Methoden bzw. Klassen, die diese Schutzmechanismen verwenden nennt man reentrant (mehrfach betretbar) bzw. thread-safe (Thread-sicher).

Da die Absicherung Zeit kostet aber nicht immer notwendig ist, gibt es in der Klassenbibliothek von einigen Klassen zwei Varianten (z.B. ArrayList/Vector).

Threads – Synchronisation

(51)

import java.util.*;

public class DigitalClock extends JFrame { JLabel timeLabel = new JLabel( "12:00:00");

public DigitalClock() { super( "DigitalClock");

setDefaultCloseOperation( EXIT_ON_CLOSE);

timeLabel.setFont( new Font( "Arial", Font.BOLD, 64));

add( timeLabel);

pack();

}

public static void main( String[] args) throws InterruptedException { DigitalClock clock = new DigitalClock();

clock.setVisible( true);

while ( true) {

GregorianCalendar cal = new GregorianCalendar ();

int h = cal.get( Calendar.HOUR);

int m = cal.get( Calendar.MINUTE);

int s = cal.get( Calendar.SECOND);

StringBuilder sb = new StringBuilder();

if ( 0==h) h = 12;

if ( h<10) sb.append( '0');

sb.append( Integer.toString( h));

sb.append( ":");

if ( m<10) sb.append( '0');

sb.append( Integer.toString( m));

sb.append( ":");

if ( s<10) sb.append( '0');

sb.append( Integer.toString( s));

clock.timeLabel.setText( sb.toString());

Thread.sleep( 1000);

} }

(52)

import java.awt.*;

import java.awt.event.*;

import javax.swing.*;

import java.util.*;

public class AnalogClock extends JFrame { public AnalogClock() {

super( "AnalogClock");

setDefaultCloseOperation( EXIT_ON_CLOSE);

add( new JComponent() {

public void paint( Graphics g) { super.paint(g);

GregorianCalendar cal = new GregorianCalendar ();

int hh = cal.get( Calendar.HOUR);

int mm = cal.get( Calendar.MINUTE);

int ss = cal.get( Calendar.SECOND);

int dx, dy;

int w = getWidth();

int h = getHeight();

g.fillOval( w/2-10, h/2-10, 20, 20);

// draw sec hand

dx = (int)(w/3 * Math.sin( 2*Math.PI/60*ss)); // lt. trigonometrischer Betrachtung am Kreis (Bogenmaß)!

dy = (int)(w/3 * Math.cos( 2*Math.PI/60*ss)); // lt. trigonometrischer Betrachtung am Kreis (Bogenmaß)!

g.drawLine( w/2, h/2, w/2+dx, h/2-dy);

} });

setSize( 400, 400);

}

public static void main( String[] args) throws InterruptedException { AnalogClock clock = new AnalogClock();

clock.setVisible( true);

while ( true) { clock.repaint();

Thread.sleep( 1000);

} }

Threads – Beispiel: Analoguhr (Fragment)

(53)

import javax.swing.*;

public class GuiThreadDemo extends JFrame { public GuiThreadDemo() {

super( "GuiThreadDemo");

setDefaultCloseOperation( EXIT_ON_CLOSE);

setLayout( new FlowLayout());

ActionListener dauerListener = new ActionListener() { public void actionPerformed( ActionEvent e) {

final JProgressBar progressBar = new JProgressBar(0, 100);

final JDialog dialog = new JDialog( GuiThreadDemo3.this, true);

dialog.setLayout( new FlowLayout());

dialog.add( progressBar);

JButton abortButton = new JButton( "abort");

final Thread worker = new Thread() { public void run() {

try {

for ( int i=0; i<=100; ++i) { final int j = i;

Thread.sleep( 50);

SwingUtilities.invokeLater( new Runnable() { public void run() {

progressBar.setValue( j);

} });

}

System.out.println( "Fertig!");

} catch ( InterruptedException ex) { // will never happen

} finally {

dialog.dispose();

} } };

(54)

abortButton.addActionListener( new ActionListener() { public void actionPerformed( ActionEvent e) {

System.out.println( "Button geklickt!");

worker.interrupt();

} });

dialog.add( abortButton);

dialog.addWindowListener( new WindowAdapter() { public void windowClosing( WindowEvent e) { System.out.println( "Fenster gekillt!");

worker.interrupt();

} });

dialog.pack();

worker.start();

dialog.setVisible( true);

} };

JButton button1 = new JButton( "Dauerlauf 1");

button1.addActionListener( dauerListener);

add( button1);

pack();

}

public static void main( String[] args) { new GuiThreadDemo3().setVisible( true);

} }

Threads – Beispiel: Dialog mit Fortschrittsanzeige(Teil 2)

(55)

Produzenten / Konsumenten (fehlt noch)

(56)

Alle Programme verarbeiten Daten in irgendeiner Form. Diese Daten sollen in der Regel über die Lebensdauer des Programms hinaus gespeichert werden.

Damit ist die dauerhafte Speicherung von Daten eine immer wiederkehrende Aufgabenstellung. Man spricht in diesem Zusammenhang auch von Persistenz (engl. to persist = continue to exist).

Prinzipiell gibt es viele Möglichkeiten, Daten zu speichern. Eine davon, die Serialisierung von Objekten haben wir bereits kennengelernt. Mit zunehmender Datenmenge steigen jedoch sowohl die Anforderungen an die Speicherung (Stichwort Transaktionen) als auch an die Geschwindigkeit des Zugriffs auf die gespeicherten Daten. Damit steigt auch die Komplexität der zugehörigen Mechanismen bzw. Algorithmen. Simple Verfahren stoßen hier an Ihre Grenzen.

Datenbanken sind auf die Funktionen Datenspeicherung und Datenzugriff spezialisierte (und hoch komplexe) Programmsysteme. Sie gestatten die Wiederverwendung von über Jahre gewachsenem Wissen und ausgereiften Algorithmen in verschiedensten Anwendungsbereichen und sind heute für jedermann erschwinglich und verfügbar (z.B. Access, mySQL)!

Datenbanken und JDBC(1) - Motivation

(57)

Grundlage dieser Aufteilung sind definierte Schnittstellen zwischen den Komponenten. Hintergedanken dabei sind wie bei Objekten die Spezialisierung und Wiederverwendbarkeit bzw. Austauschbarkeit von Komponenten einer Schicht sowie die Möglichkeit zur Lastverteilung durch Einsatz verschiedener Rechner. Da es sich jedoch zunächst um eine logische Aufteilung handelt können auch mehrere Komponenten auf einem Rechner betrieben werden.

Presentation Application

Persistence

(Webserver)

(Applicationserver)

(Datenbankserver)

(58)

Allgemein gesprochen sind Daten typischerweise in Datensätzen organisiert (z.B. eine Person, ein Artikel, ...). Diese entsprechen den Objekten in der Software. In relationalen Datenbanken wird die Darstellung von Daten mit Hilfe von Tabellen (mathematisch) formalisiert. Jedoch lassen sich Objekte eigentlich nicht formal exakt auf Tabellen abbilden (u.a. wegen der Vererbung).

Es gibt jedoch eine Abbildung, die für Praxis ausreichend ist: Dabei entspricht eine Tabelle einer Art von Datensätzen sprich einer Klasse. Eine Zeile entspricht einem Objekt dieser Klasse und die Spalten der Tabelle bilden dessen Attribute ab (vergleiche dazu die Visualisierung von Objekten mit Swing-Tabellen).

Die eindeutige Identität eines Objekts (vgl. Informatik 1) muss über ein spezielles Attribut (Primärschlüssel) abgebildet werden. Kann diese Eindeutigkeit nicht über ein oder mehrere der vorhandenen Objekte dargestellt werden, muss ein neues Attribut eingeführt werden (typischerweise eine Nummer, z.B.

Matrikelnummer, Personalnummer, Artikelnummer, ...). Der Primärschlüssel dient auch zur Speicherung von Verweisen zwischen Objekten A->B (Fremdschlüssel). Dazu gibt es in der Tabelle A eine Spalte, die den Primärschlüssel der Tabelle B enthält.

Datenbanken und JDBC(3) - Datenorganisation

(59)

:A

:B

1

1

A1:A

A2:A

A3:A

:A

:B B1:B

B2:B

Tabelle A

1 A1 2

2 A2 1

3 A3 NULL

Tabelle B

1 B1

2 B2

idA name idB

idB name

Bei einer 1:1 Beziehung sind Klassenmodell und Datenmodell strukturell identisch. Der Verweis kann wahlweise in der einen oder der anderen Tabelle abgelegt werden.

1

1

(60)

Datenbanken und JDBC(4b) – Beispiel 2

:A

:B

1

n

A1:A

A2:A

A3:A

:A

:B B1:B

B2:B

Tabelle A

1 A1

2 A2

3 A3

Tabelle B

1 B1 1

2 B2 1

idA name

idB name idA

Klassenmodell Konkrete Objektbeziehungen Datenmodell Konkrete Werte

Bei einer 1:n Beziehung sind Klassenmodell und Datenmodell immer noch strukturell identisch. Der Verweis muss jedoch auf der n-Seite der Beziehung abgelegt werden (viele verweisen auf einen), da in jeder Tabellenzelle nur ein Wert abgelegt werden kann (sog. 1ste Normalform).

1

n

(61)

:A

:B

n

m

A1:A

A2:A

A3:A

:A

:AB

:B B1:B

B2:B

Tabelle A 1 A1 2 A2 3 A3 Tabelle AB

1 1

1 2

2 1

3 1

3 2

Tabelle B 1 B1 2 B2 id name

idA idB

idB name

Bei einer n:m Beziehung kann die strukturelle Identität zwischen Klassenmodell und Datenmodell nicht mehr aufrechterhalten werden. Da in jeder Tabellenzelle nur ein Wert abgelegt werden kann müssen die Verweise in einer neuen Zwischentabelle abgelegt werden. Diese transformiert die n:m Beziehung in eine n:1 Beziehung und eine 1:m Beziehung.

n

m 1 1

(62)

Datenbanken und JDBC(4d) – Beispiel 4

Objekte Tabellen

:Auftrag Auftrag

Auftragsnummer #Auftragsnummer Datum Datum

:Position Position

Positionsnummer #%Auftragsnummer #Positionsnummer Anzahl %Artikelnummer Anzahl

:Artikel Artikel

Artikelnummer #Artikelnummer Bezeichnung Preis

Bezeichnung Preis

1..n

Dieses Beispiel zeigt nochmals die Implementierung von Beziehungen sowie die Verwendung von fachlichen Primärschlüsseln. Dabei erhält die Auftragsposition einen zusammengesetzten Primärschlüssel. Eine Verwendung von zusammengesetzten Fremdschlüsseln ist aus Aufwandsgründen nicht sinnvoll. Wird auf eine Tabelle mit einem zusammengesetzten Schlüssel verwiesen ist es daher sinnvoll, einen einfachen technischen Primärschlüssel einzuführen.

zusammengesetzter Primärschlüssel Fremdschlüssel Fremdschlüssel

Primärschlüssel

Primärschlüssel

(63)

JDBC-Treiber ODBC-Treiber

SQL (Structured Query Language) standardisiert. Die Standardisierung von SQL umfasst jedoch nur die eigentlichen Kommandos, nicht jedoch die Schnittstelle zum Austausch von Kommandos und Resultaten. Die technische Schnittstelle und deren Einbindung in eine Programmiersprache ist also zunächst herstellerspezifisch (proprietär, engl. proprietor = owner). Zur Überwindung dieser Hürde existieren weitere Standards, namentlich ODBC (Open Database Connection) und JDBC (Java Database Connection).

Speziellere Treiber bieten i.d.R. zusätzliche Funktionalität und höhere Effizienz.

X-Datenbank X-Treiber

(64)

Tabelle erzeugen

CREATE TABLE person (

id INT NOT NULL PRIMARY KEY, name VARCHAR(30)NOT NULL, vorname VARCHAR(39) NOT NULL )

Datensatz einfügen

INSERT INTO person (id, name, vorname) VALUES (5, 'Meier', 'Klaus') Datensatz aktualisieren

UPDATE person SET name = 'Maier' WHERE id = 5 Datensatz abfragen

SELECT * FROM person WHERE name = 'Maier' Datensatz löschen

DELETE FROM person WHERE id = 5 Tabelle löschen

DROP TABLE person

Datenbanken und JDBC(5) – Datenzugriff / SQL

(65)

bzw. Interfaces im Paket javax.sql sowie ein JDBC Treiber (dieser implementiert die speziellen Klassen bzw. Interfaces für eine Datenbank) benötigt.

Der Ablauf ist dabei stets wie folgt:

Treiber registrieren

Verbindung zur Datenbank herstellen Befehl(e) erzeugen

Befehl(e) abschicken Ergebnisse bearbeiten

Die dabei verwendeten Interfaces sind DriverManager

Connection

Statement bzw. PreparedStatement ResultSet

(66)

Class.forName( "sun.jdbc.odbc.JdbcOdbcDriver"); // Treiber laden Connection con = DriverManager.getConnection(

"jdbc:odbc:personal", // Datenbank URL

"administrator", // Benutzername

"geheim" // Passwort

);

Statement stmt = con.createStatement(); // Statement erzeugen int result = stmt.executeUpdate( // Update durchführen "INSERT INTO person (id name vorname)"

+ "VALUES (2, 'Meier', 'Klaus')"

);

ResultSet rs = stmt.executeQuery( // Abfrage durchführen "SELECT id name vorname FROM person WHERE name = 'Meier'"

);

while (rs.next()) { // Über Ergebnisse iterieren (je eine Zeile) int id = rs.getInt(1);

String name = rs.getString(2);

String vorname = rs.getString(3);

// Ergebnisse verwenden }

stmt.close();

con.close();

Datenbanken und JDBC(6) – Beispielprogramm (Auszug)

(67)

Definition von Fachklassen (z.B. Person) mit entsprechenden Instanzvariablen Abbildung der Fachklassen auf Tabellen mit Instanzvariablen als Spalten

Beim Zugriff auf die Datenbank werden üblicherweise stets ganze Objekte einer Fachklasse gelesen oder geschrieben. Dies hat den Vorteil, dass man innerhalb des Programms bequem mit den Fachobjekten arbeiten kann.

Um die Fachklassen dabei unabhängig von der verwendeten Datenbank- bzw.

Speichertechnologie zu halten wird dabei der eigentliche Datenbankzugriff üblicherweise in einer separaten Klasse durchgeführt. Dies kann in einfachen Programmen eine oder mehrere spezielle Klassen (DAOs) mit Methoden wie

Person fetchPerson( <Primärschlüssel>) void storePerson( Person p)

sein. Bei vernetzten Objekten mehrere fetch- und store-Methoden gegenseitig hintereinander aufrufen um ein ganzes Objektnetz zu laden oder zu speichern.

(68)

Datenbanken und JDBC(8) – Fachklassen

public class Person { public class Adresse {

int id; int id;

String name; String strasse;

String vorname; String postleitzahl;

Adresse adresse; String ort;

} }

public class Database {

public Person fetchPerson( <Primärschlüssel>) { // ...

ResultSet rs = statement.executeQuery( ...);

int id = rs.getInt( "id");

String name = rs.getString( "name");

// ...

int addressId = rs.getInt( "address_id");

return new Person(

id, name, vorname,

fetchAdresse( addressId) // Kaskadierung:

); // outside-in beim lesen

} // inside-out beim schreiben

(69)

Referenzen

ÄHNLICHE DOKUMENTE

Für den Strom, welcher einerseits von der Wärmepumpe benötigt, anderseits vom BHKW ins Netz eingespiesen wird, wurde das Modell „Electricity, low voltage, at grid/CH U“

5: Implantation unter Zuhilfenahme einer orientierenden Bohr- schablone: Das Implantat wird palatinal positioniert und nach oral angu- liert, um eine spätere palatinal

Raymer &#34;Aircraft Design: A Conceptual Approach&#34; und ist sowohl in Profi- als auch in einer Studentenversion zu beziehen.. Weitere Hinweise zum Programm gibt es im

Beachtet werden sollte dabei, dass die daraus entstehenden Entwurfsparameter erst ins Aircraft Data File übertragen werden, wenn eine erneute Berechnung im Aerodynamic- oder

Die Vertiefung Supply Chain &amp; Process Engineering befasst sich mit der Gestaltung von Lieferketten und innovativen Prozessen. Dabei werden

Die in dieser Publikation dargestellten Informationen sind vor allem für Unternehmen interessant, die E-Shops betreiben, in denen kleinere Geldbeträge in Rechnung gestellt werden

Einfache Java-Programm-

In unserem Flugbuchungssystem werden die Daten der Flugverbindungen in der Tabelle SPFLI gespeichert1. Geben Sie Kardinalität und Grad der