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