7. Das Geheimnisprinzip (D. Parnas) • • • • • In großen Softwaresystemen muss der globale Namensraum nicht nur strukturiert sondern auch eingeschränkt werden Bezeichner, die in der Spezifikation eines ADTs vorkommen, sollten öffentlich sein Bezeichner die nur für die Implementierung der Spezifikation notwendig sind, und die Implementierung selber sollten für Klienten verborgen („geheim“) bleiben Damit kann gesichert werden, dass Klienten die Daten vom ADT ausschließlich durch die spezifizierten Operationen zugreifen können Eine einmal getestete Komponente kann durch einen fehlerhaften externen Zugriff nicht zerstört werden László Böszörményi ESOP Geheimnisprinzip - 1 Sichtbarkeit in Java (visibility modifiers) • public – Von überall her zugreifbar • protected – In der definierenden Klasse – Im Paket, dem die definierende Klasse gehört – In Unterklassen – siehe später – auch in anderen Paketen • default (kein modifier) – In der definierenden Klasse – Im Paket, dem die definierende Klasse gehört – Annahme dahinter: Klassen eines Pakets sind „Freunde“ • private – Nur in der definierenden Klasse – Eine ganze Klasse kann nur dann private sein, wenn sie in eine andere Klasse eingeschachtelt ist László Böszörményi ESOP Geheimnisprinzip - 2 Stack-Klasse mit Sichtbarkeit Einstellungen public class Stack { private int top = -1; private Object[] info; // Kann beliebige Objekte stapeln // Der Stack-Zustand ist „versteckt“. // Damit ist gesichert, dass ein Zugriff nur // über die öffentlichen Methoden möglich ist public Stack(int max) { info = new Object[max]; } // Konstruktor public boolean empty () {return top < 0;} public void push (Object obj) { top++; info[top] = obj; } public Object pop() { top--; return info[top + 1]; } // Die Operationen // sind alle // öffentlich } // Stack László Böszörményi ESOP Geheimnisprinzip - 3 Gültigkeitsbereich von Bezeichnern –1. • • • Der Namensraum muss strukturiert werden Bezeichner (Namen) die in einer Klasse deklariert werden, sind in der ganzen Klasse gültig Namen einer Klasse können in Methoden der Klasse neu deklariert (redeklariert) werden – Redeklarierte Namen überdecken in ihrem Gültigkeitsbereich den ursprünglichen Namen – Nachher kommt der alte Name wieder zu Geltung • • Der Rumpf einer Methode ist ein Block Ein Block ist eine Folge von Deklarationen für lokale Variablen und von Anweisungen – Ein Name in einem Block ist gültig ab seiner Deklaration bis zum Ende des Blockes, wo er deklariert ist – Blöcke können ineinander geschachtelt werden – Eingeschachtelte Blöcke dürfen Namen der äußeren Blöcke nicht redeklarieren László Böszörményi ESOP Geheimnisprinzip - 4 Gültigkeitsbereich von Bezeichnern –2. • Die Parameter einer Methode gehören zum äußersten Block der Methode – Sie sind deshalb in der ganzen Methode gültig – Sie sind wie lokale Variablen dieses Blocks • In einem Block müssen alle Bezeichner unterschiedlich sein mit folgenden Ausnahmen – Verschiedenartige Programmelemente (Klassen, Methoden, sonstige Variablen) können gleich benannt werden – sollten aber nicht! – Methoden mit unterschiedlichen Parametertypen können gleich benannt werden (overloading) • • Klassen können ineinander geschachtelt werden Namen sind für den Menschen da – Deshalb sollten sie verständlich (aber auch nicht zu lang) sein! László Böszörményi ESOP Geheimnisprinzip - 5 Bezeichner und Blöcke – ein krankes Beispiel public class Euclid { // Klasse und Methode heißen gleich (kein Konstruktor!) static int Euclid (int a, int b) { // a und b sind redeklariert: lokal zu der Methode int Euclid; // lokale Variable heißt auch Euclid while (a != b) // a und b überdecken die äußeren a und b Namen if (a > b) a = a - b; else b = b - a; Euclid = a; // (Variable Euclid ist sogar unnötig – return a; reicht aus) return Euclid; // Es ist schlechte Praxis den gleichen Namen } // Euclid // (egal wie schön) in dieser Weise mehrfach zu benützen! static void main(String [] args) { int result; // result ist lokal zu der main Methode a = GetPosInt(); b = GetPosInt(); // a und b sind im äußeren Block deklariert result = Euclid(b, a); // Nicht der Name, sondern die Position zählt } // main static int a, b; } // Euclid László Böszörményi // a und b sind Namen der Klasse (Klassenvariablen), // sie können auch vor ihrer Deklaration verwendet werden ESOP Geheimnisprinzip - 6 Hierarchie der Namenesräume • • Namen einer Klasse sind für andere Klassen durch einen qualifizierten Bezeichner erreichbar Die qualifizierten Bezeichner haben die Form b1.b2.b3 ... und bilden einen eindeutigen hierarchischen Namen – Hierarchie geht von oben nach unten und von links nach rechts – „Kinder des gleichen Vaters“ müssen unterschiedlich heißen • Statische Variablen und Methodennamen werden durch den Klassennamen qualifiziert – Out.print bezeichnet: Klasse Out → Klassenmethode print • x Instanzvariablen werden durch den Namen einer Objektinstanz qualifiziert, wie: account1.check() László Böszörményi ESOP z y x.z.y z x.z.z ... z x.y.z y y x.y.y Geheimnisprinzip - 7 Pakete (packages) • Klassen (und Schnittstellen) können zu Paketen (packages) zusammengefasst werden – Package-Anweisung am Anfang der Klassendatei package Paketname – Pakete können zur Benützung „importiert“ werden import package.* – importiert alle Klassen des Pakets import package.class; – importiert eine Klasse import package; – letzte Komponente der Name genügt – Paketnamen können in qualifizierten Namen stehen java.lang.Integer.parseInt bedeutet: Package java.lang → Klasse Integer → Methode parseInt – Umgebungsvariable CLASSPATH • László Böszörményi Muss (meistens) die Ordner mit Paketen enthalten ESOP Geheimnisprinzip - 8 Schnittstelle (interface) • • Die öffentlichen und privaten Teile einer Klasse können – und sollen – auch syntaktisch getrennt werden Öffentliche Teile werden in eine Schnittstelle gestellt – Konstante – Abstrakte Methoden (dargestellt durch den Methodenkopf) • Die Rumpfteile der Methoden können durch Klassen implementiert werden (implements) – Eine Klasse kann mehrere Interfaces implementieren – Ein Interface kann von mehreren Klassen implementiert werden • • Die Implementierung muss die Spezifikation erfüllen Interfaces können nicht instanziert werden (mit new) – Klienten legen Objekte der implementierenden Klasse an – Sie müssen nur die Konstruktoren und die Schnittstelle kennen • Zusätzliche Eigenschaften von Java-interfaces später László Böszörményi ESOP Geheimnisprinzip - 9 Interface für die Stack-Klasse public interface Stack_I { // Stack-Interface public boolean empty (); // Ergibt wahr, wenn der Stack leer ist public void push (Object obj); // Legt ein Element auf den Stack // empty (push (s, x)) = false; // Vorbedingung: Muss noch Platz da sein public Object pop(); // Gibt oberstes Element vom Stack zurück // pop (push (s, x)) = (s, x) // Vorbedingung: Stack darf nicht leer sein } // Stack_I • Die Stack-Implementierungen müssen diese Methoden realisieren • Die Konstruktoren sind in den implementierenden Klassen zu finden László Böszörményi ESOP Geheimnisprinzip - 10 Array-Implementierung vom Stack-Interface public class StackArray implements Stack_I { // Implementiert Stack-Interface protected int top = -1; protected Object[] info; // Stack-Zustand kann in Klassen des // gleichen Pakets // und in Subklassen zugegriffen werden public StackArray (int max) { info = new Object[max]; } // Konstruktor public boolean empty () {return top < 0;} public void push (Object d) { top++; info[top] = d; } public Object pop() { top--; return info[top + 1]; } } // StackArray László Böszörményi ESOP Geheimnisprinzip - 11 Implementierung mit Zusicherungen public class StackArray implements Stack_I { // Implementiert Stack-Interface protected int top = -1; // Stack-Zustand kann nur im Paket protected Object[] info; // und in Subklassen zugegriffen werden public StackArray (int max) { info = new Object[max]; } // Konstruktor static final void Assert(boolean b) throws java.lang.Error { ) throw java.lang.Error ("Falscher Aufruf"); if ( !bif )( !b throw new new java.lang.Error ("Falscher Aufruf"); } // Assert; public boolean empty () {return top < 0;} public void push (Object obj) { Assert( Assert( top top << info.length info.length -- 11 ); ); top++; info[top] = obj; } // pusch public Object pop() { Assert( !empty() ); top--; return info[top + 1]; } } // StackArray László Böszörményi ESOP Geheimnisprinzip - 12 Listen • • • • • • • • Eine Liste verbindet eine Reihe von Knoten Knoten können eingefügt und gelöscht werden Die Knoten enthalten beliebige Informationen Sie enthalten eine Referenz auf einen Nachbarknoten – auf den „nächsten“ (next) Das Ende der Liste ist ein Knoten mit next == null Ein Ring ist eine Liste ohne Ende (die Knoten bilden einen Kreis) In doppelt verketteten Listen enthalten die Knoten je eine Referenz auf beide Nachbarn In sortierten Listen werden die Knoten nach einem Ordnungskriterium eingefügt – Z.B. nach einer Nummer, die als „Schlüssel“ dient • Weiteres über Listen siehe später László Böszörményi ESOP Geheimnisprinzip - 13 Listen als Stack • Die einfachsten Listen sind als Stack organisiert – Sowohl Einfügen als auch Löschen geschieht am Kopf (head) – x zeigt auf das einzufügende Element Löschen: head = head.next; head info next 2. x info next info next info next null 1. Einfügen: x.next = head; head = x; info next László Böszörményi ESOP Geheimnisprinzip - 14 Listen-Implementierung vom Stack-Interface -1. public class StackList implements Stack_I { // Implementiert Stack-Interface mit Liste protected class Node { // Klasse Node definiert die Listenstruktur Object info; Node next; // info ist ein belibiges Objekt, next zeigt auf Node (Object i) {info = i; next = null; } // das nächste Node-Objekt } // Node protected Node head; // Kopf der Liste StackList () { head = null; } // Konstruktor public boolean empty () { return head == null; } // empty // Leer, wenn der Kopf auf nichts zeigt László Böszörményi ESOP Geheimnisprinzip - 15 Listen-Implementierung vom Stack-Interface -2. public void push (Object obj) { // Legt einen neuen Knoten vorne an Node x = new Node(obj); /* x zeigt auf den neuen Knoten (mit info als Inhalt) */ x.next = head; // Einfügen head = x; } // push public Object pop() { Object x = head.info; head = head.next; return x; } // pop // Entfernt den Knoten am Kopf // x zwischenspeichert das Info- Objekt // Entfernen aus der Liste // Info zurückgeben } // StackList László Böszörményi ESOP Geheimnisprinzip - 16 Benutzerklasse vom Stack-Interface public class StackClient { // Methode StackFractions is polymorph (siehe später): // sie funktioniert für alle Stack-Arten die die Schnittstelle Stack_I implementieren static void StackFractions (Stack_I stack, int N, Fraction x) { for (int i = 0; i < N; i++) { // „Pusht“ N Brüche x, x/2, x/4 . . . x/2N-1 stack.push(x); x = Fraction.times(x, new Fraction(1, 2) ); } // for i while ( !stack.empty() ) { // „Popt“ und zeigt alle Elemente am Stack System.out.print( stack.pop() + " " ) ; } // while stack not empty System.out.println(); } // StackFractions static void main(String [] args) { // Ruft StackFractions mit 2 verschiedenen Stacks StackFractions(new StackArray(8), 8, new Fraction (3, 4)); StackFractions(new StackList(), 9, new Fraction (1, 2)); } // main 3/512 3/256 3/128 3/64 3/32 3/16 3/8 3/4 } // StackClient 1/512 1/256 1/128 1/64 1/32 1/16 1/8 1/4 1/2 László Böszörményi ESOP Geheimnisprinzip - 17 Eingabe/Ausgabe • Datei – Benannte Sammlung von Daten, meistens persistent • Datenstrom – Sequenz von Daten eines Typs (byte, int, etc.) – Abbildung auf Tastatur, Bildschirm, Datei etc. • Die Klassen In und Out sind auch für Dateien verwendbar – open, close: zur Eröffnung bzw. Schließung einer Datei – done: zur Abfrage des Erfolgs einer E/A Operation • Objekte von Klassen, die das Interface Serializable implementieren, werden automatisch serialisiert, und können in dieser Form direkt in eine Datei geschrieben werden • Mehr über Persistenz der Daten in weiteren LVs László Böszörményi ESOP Geheimnisprinzip - 18 Aus- und Eingabe eines Zahlenstroms public class TestIO { static final String FileName = "Vector"; // Name der Datei static void main(String [] args) { int [] v1 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; Out.open(FileName); // Neue Datei zum Schreiben erzeugen if (Out.done() ) { // Wenn erfolgreich: for (int i = 0; i < v1.length; i++) Out.println(v1[i]); Out.close(); // Persistent: erst durch Schließen 0 } else { Out.println("File " + FileName + " cannot be created"); } 1 In.open(FileName); // Eröffne vorhandene Datei 2 if (In.done()) { // Wenn erfolgreich: 3 int i = In.readInt(); 4 5 while (In.done()) { // Solange Datei nicht endet: 6 Out.println(i); i = In.readInt(); // weiterlesen 7 } // while 8 In.close(); // Eingabedatei schließen 9 } else { Out.println("File " + FileName + " not found"); } } } // main, TestIO László Böszörményi ESOP Geheimnisprinzip - 19 Programm liest eigenen Text – 1. public class PrintSelf { static final String FileName = "PrintSelf.java"; static void ReadFile1 (String fileName) { In.open(FileName); // Datei eröffnen if (In.done()) { // Wenn erfolgreich char ch = In.read(); // Byteweise einlesen while (In.done()) { // Solange Datei nicht endet Out.print(ch); ch = In.read(); // weiterlesen } // while In.close(); // Eingabe schließen } else { Out.println("File " + FileName + " not found"); } } // ReadFile1 László Böszörményi ESOP Geheimnisprinzip - 20 Programm liest eigenen Text – 2. static void ReadFile2 (String fileName) { In.open(FileName); // Datei eröffnen if (In.done()) { // Wenn erfolgreich String s = In.readFile(); // Liest die ganze Datei! Out.println(s); // Gibt den ganzen String aus In.close(); // Eingabe schließen } else { Out.println("File " + FileName + " not found"); } } // ReadFile2 static void main(String [] args) { ReadFile1(FileName); ReadFile2(FileName); } // main } // PrintSelf László Böszörményi // Gibt die Datei PrintSelf.java aus // Gleiche Datei nochmals aus ESOP Geheimnisprinzip - 21 Kategorisierung von Klassen • Werkzeugkasten (toolbox) (z.B. Klasse Math) – Bieten zusammengehörende statische Funktionen an – Sie sind nicht instanzierbar – Sie sind zustandslos • Module (Klasse mit der main Methode, oder In und Out) – Haben (ausschließlich) statische Variablen und Methoden – Sie sind nicht instanzierbar – Sie haben einen einzigen (statischen) Zustandsraum • Klassen für Typbildung (ADTs) (z.B. die Klasse Fraction) – Sie sind instanzierbar – Jedes Objekt hat eigenen Zustandsraum (Instanzvariablen) • Mischungen sind eher zu vermeiden! László Böszörményi ESOP Geheimnisprinzip - 22