Warum Programmieren lernen?
You do not really understand
something unless you can explain it
to your grandmother computer.
-
Albert Einstein
Programmieren lernen ist komplex
... wie das Erlernen einer normalen Sprache (Syntax & Vokabular)
Zwei Prinzipien zum Umgang mit Komplexität:
Iteration
Abstraktion
Iteration
•
Komplexe Dinge kann man nicht von vorn nach hinten lernen
•
Wichtige Konzepte werden wieder und wieder auftauchen
•
Sie werden diese Konzepte besser und besser verstehen
•
Eigene Erfahrungen machen, bzw. Anwenden und Üben, ist ein
essenzieller Teil dieses Prozesses
Abstraktion
Verschiedene Perspektiven
Wechsel zwischen Wie? und Warum?, zwischen Anwenden und
Analysieren
Allgemeine Prinzipien finden
Visualisierungen (Grafiken, Diagramme, etc.) sind hilfreich
Analogien sind hilfreich
Java...
... ist wie ein Lego Technik Baukasten
Beim Programmieren geht es darum...
... die Bauteile so
zusammenzubauen,
dass das Modell
funktioniert ...
... und fehlende Bauteile zu definieren
•
Bauteil = Objekt
•
Definition eines Bauteils =
Klasse
•
Was das Bauteil kann =
Methoden
•
Anleitung zur Benutzung
der Bauteile = API
•
Sammlung von Bauteilen =
Bibliothek
Greenfoot ist ...
•
Ein GUI, das mit Java
programmiert wurde
Sie müssen herausfinden,
wie man das GUI benutzt
•
Eine IDE, in der Sie zwei
vordefinierte Klassen (World und Actor) mit
allem erweitern können, was die Sprache Java
zu bieten hat
Sie müssen herausfinden, wie Sie dem
Computer sagen, was die Welt und die
Akteure tun sollen (in Java)
Das GUI von Greenfoot
(ist übrigens selbst ein in Java definiertes Objekt)
Projekt, package
(Dateiformat?,
Speicherort?)
== README.txt
Die beiden
erweiterbaren
Klassen
englische APIs
für Greenfoot &
Java (online)
Programmablauf
beobachten,
Objekte und
Methoden
testen
Editor = IDE
Kompilieren???
Der Editor (IDE) von Greenfoot
Kompilieren???
(hier braucht es
die JDK)
documentation
= API für diese
Klasse (aus
Kommentaren)
Name der Klasse
Eine Methode
Formatierung
(syntax checking
highlighting,
indentation,
undo, ...)
kompilieren = erstellen, übersetzen, zusammentragen
Programmiersprache
(Quellcode, Text, für
Menschen verarbeitbar)
Compiler
Maschinensprache
(Binärcode, Nullen & Einsen,
für Prozessoren verarbeitbar)
Programm in C
CCompiler
CCompiler
CCompiler
kompilieren = erstellen, übersetzen, zusammentragen
Sprache = Java
(Quellcode, Text, für
Menschen verarbeitbar)
Java-Compiler
Byte-Code
(für JRE/JVM verarbeitbar)
Maschinensprache
(Binärcode, Nullen & Einsen,
für Prozessoren verarbeitbar)
Arrays & Datenstrukturen
Datenstruktur = mehrere Werte
(unter einem Variablennamen)
•
•
•
•
In Java ist eine
Datenstruktur
eigentlich eine Klasse,
deren Aufgabe es ist,
mehrere Werte zu
verwalten
Der Oberbegriff (und
auch das Interface)
dafür ist „Collections“
Die Abbildung rechts
zeigt nur einige davon
Den klassischen Array gibt es hauptsächlich aus historischen
Gründen, er ist einfach, aber unsauber
Arrays
•
Ein Array ist eine (unsaubere) Datenstruktur
•
Ein Array ist so etwas wie ein Regal, in dessen Fächern
jeweils ein Wert gespeichert wird
•
In einem Array kann man mehrere Daten gleichen Typs
unter einem Namen speichern
•
In Java können Arrays ihre Grösse (=Anzahl Elemente)
NICHT ändern
•
Leere Elemente haben den Wert null
•
Das erste Element hat den Index 0!
Array:
typ[] name = {Werte};
Regal mit gleichartigen Kisten:
name[0] Inhalt der ersten Kiste
Beispiel:
int[] arr = {3, 5, 0, 17};
// leeres Array: int[] arr = new int[4];
System.out.println(arr[3]);
// 17
arr[1] = 11; // an 2. Stelle 11 statt 5
int x = arr[0];
// x ist 3
int l = arr.length; // l ist 4
ObjektOrientiertes Programmieren
Konzepte, Begriffe, Beispiele – und ein wenig UML
Konzepte des OOP
Datenkapselung
Vererbung
Überschreiben
Überladen
Polymorphie
Datenkapselung
ist eines der wichtigsten Grundprinzipien des OOP.
bezeichnet den kontrollierten/eingeschränkten
Zugriff auf Methoden bzw. Attribute von Klassen
wird in JAVA durch Zugriffsmodifizierer umgesetzt,
die die Sichtbarkeit (und damit Aufrufbarkeit) für
externe Klassen kontrollieren:
public (öffentlich) von außen für jeden sichtbar
private (privat) nur intern (in der Klasse) sichtbar
Datenkapselung
Das Ziel eines sauberen Klassendesigns ist es zu
erreichen, dass Klassen bzw. Objekte nur über
wenige, wohl-definierte Schnittstellen (= öffentliche
Methoden) mit anderen Klassen interagieren.
Vom Innenleben einer Klasse soll der Verwender
(Client-Klassen bzw. auch der Programmierer)
möglichst wenig wissen müssen (Geheimnisprinzip).
So kann Software maximal modularisiert werden
überschaubar, stabil, flexibel & erweiterbar
Regeln für Zugriffsmodifizierer:
Der Zugriff auf Bestandteile einer Klasse sollte
immer maximal eingeschränkt werden ("so privat
wie möglich").
Instanzvariablen (d.h. Attribute) werden private
deklariert. Zugriff von ausserhalb der Klasse erfolgt
über public getter- und setter-Methoden.
so kann man zwischen Lese- und Schreibrechten
unterscheiden
Konstruktoren werden public deklariert.
Faustregeln für Methoden
keine Methode sollte mehr als 10 Zeilen Code enthalten,
sonst in mehrere Aufteilen
jede Methode sollte genau eine Aufgabe haben, dann ist
sie auch einfach zu benennen
Gut organisierten Code kann man fast wie normalen Text
lesen (dann muss man auch weniger kommentieren)
Methoden sollten möglichst eigenständig sein
Methoden sollten nur dann public sein, wenn es nicht
anders geht (= Schnittstellen mit anderen Klassen)
Gründe für Methoden
Code organisieren durch Unterteilung in kleine,
wiederverwertbare, eigenständige Einheiten
Code einsparen
durch wiederverwendbare Methoden
Code übersichtlich gestalten
durch geschickt benannte Methoden
Code flexibel gestalten
durch Methoden mit Übergabewerten
Geheimnisprinzip (Sichtbarkeit einschränken)
Vorteile der Datenkapselung
Weniger unerwünschten Interaktionen zwischen
Programmteilen, dadurch weniger Bugs
Erhöhte Übersichtlichkeit, da meist nur die
öffentliche Schnittstelle einer Klasse (API) betrachtet
werden muss
Erhöhte Flexibilität durch Modularität, einzelne
Klassen oder Methoden können verändert oder
ausgetauscht werden, ohne den Rest des Programms
zu beeinflussen.
Vererbung in Java
Eine Unterklasse erbt alle Eigenschaften (Attribute und
Methoden) einer Oberklasse.
IST-Beziehung
Beispiel Vererbung
+ ! zeichneKreis
Buntstift erweitert die Funktionalität von Stift
(Spezialisierung)
Beispiel Vererbung
class Figurstift extends Buntstift
{
public Figurstift()
{
super();
//Konstruktor der Elternklasse benutzen
}
public void zeichneQuadrat(double s)
{
zeichneRechteck(s, s); //muss auch in Figurstift
sein
}
}
Vererbungshierarchie
Wenn man dieselbe Methode
in verschiedene Klassen
kopiert, ergeben sich
Probleme – spätestens wenn
man die multiplen Kopien
ändern will
Meist hätte man das besser
mit Vererbung gelöst, also
nur eine Version der
Methode in einer
gemeinsamen Elternklasse.
Vererbungshierarchie
Alle Klassen erben von Object
"super"
Methode der Oberklasse aufrufen:
super.methodenName();
Normalerweise werden Methoden in Oberklassen automatisch gefunden.
super braucht man nur, wenn dieselbe Methode in der eigenen Klasse (this)
auch existiert (also überschrieben wurde), man aber diejenige der
Oberklasse aufrufen will
Konstruktor der Oberklasse aufrufen:
super();
Da es in allen Klassen immer einen Konstruktor gibt (er also immer
überschrieben ist), benötigt man das super() oft, um denn Konstruktor der
Oberklasse aufzurufen
"this"
this wird gebraucht, wenn eine Instanz einen
Auftrag an sich selber schickt (kann man meist weg
lassen), oder wenn die Instanz sich selbst als
Parameter übergeben will
onMousePressed(this);
Vorteile der Vererbung
Erhöhte Übersicht in einem Klassendesign
Durch die Vererbung lassen sich logische Hierarchien
abbilden
Weniger Quellcode nötig
Code der Oberklasse wird in Unterklassen
wiederverwendet
Einfachere Wartung
Änderungen müssen nur an einer Stelle durchgeführt
werden
Vorteile der Vererbung (Beispiel)
- name, adresse, telefon müssen nur einmal programmiert werden.
- Ein neues Attribut alter muss nur an einer Stelle eingefügt werden.
Überschreiben
wenn Methoden oder Attribute in Kind- UND
Elternklasse definiert sind
(die überschriebene Methode kann mit super.f() noch immer aufgerufen werden)
Vorteile des Überschreibens
Im Unterschied zur Vererbung (Erweiterung der
Elternklasse) kann man durch Überschreiben Teile
des Verhaltens der Elternklasse verändern
Ein Beispiel ist die Methode toString(), die bereits in
der grundlegendsten aller Java-Klassen (Object)
definiert ist, dann aber in fast allen Unterklassen
erneut definiert wird
um einen String zu erzeugen, der das Wichtigste
über diese Instanz in druckbarer Form angibt
Überladen
mehrere Versionen einer Methode, die sich nur in
Typ und/oder Anzahl der Übergabeparameter
unterscheiden
häufig beim Konstruktor eingesetzt
die zum Aufruf passende Version wird vom Compiler
automatisch erkannt
Beispiel Überladen
class Figurstift {
Color col;
public Figurstift()
{
col = new Color(255, 0, 0); //default-Wert für col
}
public Figurstift(Color initcol)
{
col = initcol; //bei Erschaffung übergebener Wert
//z.B. new Figurstift(new Color(255,0,0));
}
}
Vorteile des Überladens
Implementierung von optionalen
Übergabeparametern (default values)
Zeit- und Tipparbeit-Ersparnis beim Gebrauch einer
Klasse durch die Definition mehrerer Konstruktoren.
Setzen von Attributen beim Erschaffen der Instanz
Polymorphie
(griechisch: Vielgestaltigkeit)
beschreibt die Fähigkeit eines Bezeichners, abhängig von
seiner Verwendung unterschiedliche Datentypen
anzunehmen.
Jedes Objekt kann auch den Typ seiner Elternklasse(n)
annehmen
jedes Java-Objekt hat die Grundklasse Object
Objekte können in kompatible Typen gecastet werden,
z.B.
int i = 1;
double d = (double) i;
Joe joe1 = new Joe();
Actor a = (Actor) joe1;
Object o = (Object) joe1;
Beispiel Polymorphie
public class Polymorphie {
double flaeche = 0;
Rechteck re1 = new Rechteck( 3, 4 );
Figur re2 = new Rechteck( 5, 6 );
Kreis kr1 = new Kreis( 7 );
Figur kr2 = new Kreis( 8 );
Vector vec = new Vector(re1, re2, kr1, kr2);
// Berechne die Summe der Flaechen aller Figuren:
for( int i=0; i<vec.size(); i++ ) {
Figur f = (Figur)(vec.get( i ));
flaeche += f.getFlaeche(); //benutzt die (evtl. über//schriebene) Methode getFlaeche der jeweiligen Unterklasse
}
System.out.println( "Gesamtflaeche ist: " + flaeche ); }
}
Vorteile von Polymorphie
einheitlicher Aufruf von überschriebenen Methoden
(z.B. toString())
dynamische Typumwandlung macht Vererbung erst
effizient nutzbar
insgesamt spart man dadurch Tipp- und
Organisationsaufwand und bekommt
übersichtlicheren Sourcecode
ObjektOrientiertes Programmieren ...
... ist hauptsächlich UML
OOP-Begriffe
Abstraktion
Reduktion der Wirklichkeit auf das
Wesentliche
Bauplan für Objekte
Modellieren
UML (Unified Modeling Language)
Standardisierte Art, das Modell in
Diagrammen darzustellen
Fachbereich des Software-Architekten
Objekt == Instanz
Konkrete Realisierung (Instanziierung)
einer Klasse
Die Abstraktion in einem SoftwareModell beschreiben
Klasse
Attribut == Instanzvariable
Eigenschaft eines Objekts
Methode
Fähigkeit eines Objekts
Softwareentwicklung
Code schreiben
Code organisieren
spez. Programmiersprache
UML
einfüllen des Codes im Body
planen der Interaktion &
von Methoden
Schnittstellen von Klassen
„Codemonkeys“
Systemarchitekten
Code schreiben ist einfach, Code organisieren ist schwer
(und deutlich besser bezahlt)
UML =
unified modeling language
UML-Klassendiagramm
stellt den Aufbau der einzelnen Klassen dar (wie JavaDoc)
stellt Zusammenhänge zwischen Klassen dar
Aufbau von Klassen
Rectangle
Modifikator
+ für public
- für private
Typ des Übergabewerts
Typ des Rückgabewerts
-length : double
-width : double
+setLength(len : double) : void
+setWidth(w : double) : void
+getLength() : double
+getWidth() : double
+getArea() : double
Name der Klasse
Attribute der Klasse
Methoden der Klasse
Zusammenhänge zwischen Klassen
class B extends A {
…
}
Vererbung
B „ist ein“ A
B „ist Spezialisierung von“ A
class B implements A {
…
}
Realisierung eines Interface
class A {
B b;
}
Abhängigkeit
class A {
B.init();
}
A „hat/benötigt/benutzt“ B
s. UML-Symbole.doc oder
UML/Klassendiagramm.pdf
class A {
B b;
}
gerichtete Assoziation
class A {
B b = new B(this);
void xxx() {…}
… b.yyy(); …
}
bidirektionale Assoziation
class B {
A a;
public B(A a) {
this.a = a;
}
public yyy() {…}
… a.xxx(); …
}
class A {
B b;
}
A „verwendet“ B
A „verwendet“ B
B „verwendet“ A
Jede Seite muss eine Referenz der anderen Seite
haben. Problem einer Inkonsistenz!
A verwendet B. Wenn B auch A verwendet
(indem B auf Datenfelder oder Methoden des
Objektes A zugreift), muss B eine Referenz auf
das Objekt A haben ("Callback").
Komposition
A „hat/besteht aus“ B
B ist ohne A nicht existent und nicht zu benutzen.
Eine Klasse erweitern
class A extends B (Vererbung)
Unterklasse A erbt alle Methoden von Oberklasse B
A ist also eine spezialisierte Form von B („isA“)
Java erlaubt nur eine einzige Oberklasse
class A implements I (Interface)
Das Interface I definiert Methoden (ohne Code)
A wird gezwungen, die in I definierten Methoden zu
haben
Eine Klasse kann mehrere Interfaces implementieren
Eine Verbindung schaffen
Damit eine Instanz von A mit einer Instanz von B
reden kann, braucht sie einer Referenz zu b
class A {
B b;
MethodeX/Konstruktor () {
b = new B();
}
}
class B {
in MethodeX/Konstruktor () {
new A(this);
}
}
class A {
B b;
MethodeX/Konstruktor (B b) {
this.b = b;
}
}
Auch indirekte Verbindungen über eine dritte Klasse
C sind möglich
Aufgabe
Zeichnen Sie ein möglichst vollständiges UML-
Klassendiagramm des Joe2.0 Szenarios
(einschliesslich der benutzten Greenfoot-Klassen)
Ist Greenfoot eher Library oder Framework?
externe Bibliotheke
eine oder mehrere zur Verfügung gestellte Klassen,
oft mit internen Abhängigkeiten ( UML)
Es gibt zwei Möglichkeiten (oft gemischt):
1. Eigene Klassen erweitern Fremde (=Framework)
class meineKlasse extends fremdeKlasse {}
class meineKlasse implements fremdeKlasse{}
2. Eigene Klassen benutzen Fremde (=Library)
import fremdesPaket.fremdeKlasse;
FremdeKlasse xxx = new FremdeKlasse();
Software Development
Wasserfallmodell
Requirements
Design
Implementation
Verification
Maintenance
Iterative Development
Design
Requirements
Implementation
Initial Idea
Iteration
Planning
Testing
Evaluation
Deployment
Test Driven Development
Design
Requirements
run test and
see if it fails
(as Tests)
Initial Idea
write code to
cover the test
add a test
Planning
Implementation
refactor
run again and
see if it passes
Testing
(automated)
Evaluation
Deployment
Schlussfolgerungen:
•
Mehrstufige Grobplanung
•
Detailüberlegungen nur für die gegenwärtige Stufe – nicht zu weit
voraus planen
•
Tests für alles, was nicht trivial ist
•
Tests als erstes Formulierung der Anforderungen, explizite
Zielvorstellung
•
Jeweils nur an einem Problem arbeiten – auch hier helfen Tests,
nacheinander abarbeiten
•
Keine Angst vor refactoring/überarbeiten – Werkzeuge benutzen