Fortgeschrittene Aspekte objektorientierter Programmierung Prof. Dr. Arnd Poetzsch-Heffter — AG Softwaretechnik Wintersemester 02/03 Inhalt 3 Inhalt 1. 2. 3. 4. 5. 6. Einleitung Grundlagen objektorientierter Sprachen Techniken zum Prüfen objektorientierter Programme Spezifikation objektorientierter Programme Verifikation spezifizierter Eigenschaften Ausblicke A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 1. Einleitung 5 1. Einleitung 1.1 Überblick 1.2 Formale und notationelle Grundlagen A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 1.1 Überblick 6 1.1 Überblick A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 1.1 Überblick Ausgangspunkt: • Kenntnis objektorientierter Programmierung (z.B. Java) Lernziele: • technische Vertiefung in: ◦ Semantik ◦ Typisierung . bessere Parametrisierung . Ausdruck weiterer Eigenschaften ◦ Spezifikation von Programmeigenschaften ◦ Prüfen und Verifikation A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 7 1.1 Überblick • konzeptionelle Vertiefung in: ◦ objektorientierter Programmierung ◦ objektorientierter Modellierung • Werkzeugentwicklung in diesem Bereich A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 8 1.1 Überblick 9 Semantik Zwei zentrale Aspekte: • Spezifikation von Programmeigenschaften • Spezifikation der Programmiersprache Beispiel(Semantik für Programmeigenschaften): public class List { int length; ListElems le; //@ invariant length == le.leng(); ... } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 1.1 Überblick Beispiel(Programmiersprachensemantik): class IntPair { private int a; private int b; IntPair( int ai, int bi ){ a = ai; b = bi; } int sum(){ return this.add(); } private int add(){ return a+b; } } class IntTriple extends IntPair { private int a; IntTriple( int ai,int bi,int ci ){ super(ai,bi); a = ci; } int sum(){ return this.add(); } private int add(){ return super.sum() + a; } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 10 1.1 Überblick public class PrivateTest { public static void main( String[] arg ) { IntPair ip = new IntPair(3,9); IntTriple it = new IntTriple(1,2,27); System.out.println( ""+ip.sum()+" "+it.sum() ); } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 11 1.1 Überblick 12 Typisierung Typisierung: Beschreibung von einfachen Eigenschaften • im Rahmen der Programmiersprachen • mit automatischer Prüfung durch den Übersetzer A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 1.1 Überblick Beispiel(Parametrischer Polymorphismus): class LinkedList<ET> { Entry<ET> header = new Entry<ET>(null, null, null); int size = 0; LinkedList() { ... } ET getLast() { ... } ET removeLast() { ... } void addLast(ET e) { ... } int size() { return size; } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 13 1.1 Überblick class Entry<ET> { ET element; Entry<ET> next; Entry<ET> previous; Entry(ET element, Entry<ET> next, Entry<ET> previous) { this.element = element; this.next = next; this.previous = previous; } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 14 1.1 Überblick class Test { public static void main( String[] args ) { LinkedList<String> ls = new LinkedList<String>(); ls.addLast("erstes Element"); ls.addLast("letztes Element"); ls.getLast().indexOf("Elem"); // liefert 8 LinkedList<Object> lo = new LinkedList<Object>(); lo.addLast( new Object() ); lo.addLast( new Object() ); lo.getLast().indexOf("Elem"); // Programmierfehler, } // der vom Uebersetzer automatisch entdeckt wird } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 15 1.1 Überblick Spezifikation von Programmeigenschaften Spezifikation: • Beschreibung aussagekräftiger Eigenschaften ◦ von Programmteilen oder -Schnittstellen ◦ mit zusätzlichen Sprachmitteln • Prüfung mit unterschiedlichen Techniken A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 16 1.1 Überblick Beispiel(Methodenspezifikation): public class IntMathOps { /*@ public normal_behavior @ requires y >= 0 @ modifiable \nothing @ ensures \result * \result <= y @ && y < (Math.abs(\result)+1) @ * (Math.abs(\result)+1) ; @*/ public static int isqrt( int y ){ ... } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 17 1.1 Überblick 18 Prüfen und Verifikation Aufgabe: Nachweis, dass beschriebenen Eigenschaften von Implementierung erfüllt werden. Techniken: • vollautomatisch vs. halbautomatisch/interaktiv • vollständig vs. teilweise • statisch vs. dynamisch A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 1.1 Überblick Beispiel(Korrektheit): Aufgabe: Beweise, dass folgende Implementierung der Methode isqrt obige Spezifikation erfüllt. public static int isqrt( int y ){ int count = 0, sum = 1; while (sum <= y) { count++; sum += 2 * count + 1; } return count; } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 19 1.1 Überblick Formale Techniken auf der Entwurfsebene Exemplarisch betrachten wir UML-Klassendiagramme annotiert mit OCL-Bedingungen (Object-constraint language) Beispiel(Annotiertes Klassendiagramm): A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 20 1.1 Überblick Statisches Modell mit Bedingungen A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 21 1.1 Überblick 22 Aufbau, Inhalt, Literatur 1. Einleitung ◦ Überblick ◦ formale und notationelle Grundlagen 2. Grundlagen objektorientierter Sprachen ◦ Spracheigenschaften ◦ Semantik objektorientierter Sprachen ◦ Eigenschaften objektorientierter Programme ◦ Modularität und Kapselung 3. Techniken zum Prüfen objektorientierter Programme ◦ Einführung in die Bedeutung von Typsystemen A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 1.1 Überblick ◦ Typinformation und ihre statische und dynamische Prüfung ◦ Typsicherheit ◦ parametrische Typsysteme ◦ virtuelle Klassen ◦ erweiterte Typsysteme 4. Spezifikation objektorientierter Programme ◦ Grundlagen, Abstraktion, Kapselung ◦ Spezifikation funktionaler Eigenschaften ◦ Konformität von Subklassen (behavioral subtyping) ◦ Spezifikation von Umgebungseigenschaften ◦ Klassen- und Modulinvarianten ◦ Modularitätsproblematik A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 23 1.1 Überblick ◦ Verfeinerung von Spezifikation 5. Verifikation spezifizierter Eigenschaften ◦ erweitertes dynamisches Prüfen ◦ erweitertes statisches Prüfen ◦ Programmverifikation ◦ Strategien zur interaktiven Verifikation ◦ Generierung schwächster Vorbedingungen 6. Ausblicke A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 24 1.1 Überblick Literatur: • kein Buch, das gesamten Stoff abdeckt • Bücher über Vorlesungsteile • wissenschaftliche Artikel • Sprachspezifikationen Literatur wird kapitel-/abschnittsweise angegeben. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 25 1.2 Formale und notationelle Grundlagen 1.2 Formale und notationelle Grundlagen A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 26 1.2 Formale und notationelle Grundlagen • Die Sprache der (sortierten) Prädikatenlogik : ◦ Signaturen ◦ Terme ◦ Formeln • Algebraische Datentypen • Abschließende Bemerkungen A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 27 1.2 Formale und notationelle Grundlagen Die Sprache der Prädikatenlogik Definition(Signatur): Eine Signatur Σ = (S, F ) besteht aus • einer Menge S von Sorten (Namen für Datentypen) • einer Menge F von Funktionssymbolen (Namen für Funktionen) so dass jede f ∈ F eine Funktionalität f : s1 × . . . × sn → s hat mit s1, . . . , sn, s ∈ S (n ≥ 0) Nullstellige Funktionen werden Konstanten genannt. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 28 1.2 Formale und notationelle Grundlagen Beispiel(Signatur): 1. Signatur der ganzen Zahlen: S F ΣINTEGER = {Integer} = {0 : → Integer succ : Integer → Integer pred : Integer → Integer + : Integer × Integer → Integer − : Integer × Integer → Integer . . .} = (S, F ) A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 29 1.2 Formale und notationelle Grundlagen 2. Signatur für Keller mit Elementen aus der Sorte Elem S F ΣSTACK = {Elem, Stack } = {emptyStack : → Stack push : Stack × Elem → Stack pop : Stack → Stack top : Stack → Elem} = (S, F ) A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 30 1.2 Formale und notationelle Grundlagen 31 Bemerkung: • Im Folgenden gehen wir davon aus, dass ◦ jede Signatur die Sorte Bool enthält und ◦ für jede Sorte S eine Gleichheitsfunktion =S : S × S → Bool existiert. • Sorten spielen auf Spezifikationsebene die gleiche Rolle wie Typen in der Programmierung. Die terminologische Unterscheidung erleichtert im Folgenden die Trennung zwischen ◦ der Sprache, die beschrieben wird, und ◦ der Sprache, die zur Beschreibung verwendet wird. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 1.2 Formale und notationelle Grundlagen 32 Definition(Terme): Jede Signatur Σ = (S, F ) und S-sortierte Menge VAR von (logischen) Variablen erzeugt eine Menge T (Σ, VAR) von Termen, die aus Funktionssymbolen aus F und Variablen aus VAR (entsprechend der Funktionalitäten der Funktionssymbole und der Sorten der Variablen) bestehen. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 1.2 Formale und notationelle Grundlagen Beispiel(Terme): • Sei VARSTACK = {e : Elem, s : Stack}, push(s, e), pop(push(s, e)), top(pop(push(s, e))), top(push(emptyStack, e)) sind Terme aus T (ΣSTACK , VAR STACK ) • succ(0), succ(0) + succ(succ(0)) ∈ T (ΣIN T EGER, ∅) A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 33 1.2 Formale und notationelle Grundlagen 34 Definition(Formeln): Sei Σ = (S, F ) eine Signatur, VAR eine S-sortierte Menge logischer Variablen und sei die Menge der Σ–Terme der Sorte s, bezeichnet als T (Σ, VAR)s. Die Menge der Σ–Formeln F (Σ) ist die kleinste Menge, die die folgenden Eigenschaften erfüllt: • jeder Term der Sorte Bool ist in F (Σ ); • wenn G, H ∈ F (Σ ), dann sind ¬G, (G ∧ H), (G ∨ H), (G ⇒ H) und (G ⇔ H) in F (Σ ); • wenn Xs ∈ VAR s und G ∈ F (Σ ), dann (∀Xs : G), (∃Xs : G) ∈ F (Σ ). A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 1.2 Formale und notationelle Grundlagen Bemerkung: • Um Klammern zu vermeiden, benutzen wir die folgenden Präzedenzen (von starke zu schwache Bindung): ¬, ∧, ∨, ⇒, ⇔, ∀, ∃. • Prädikatenlogik wird im Folgenden hauptsächlich als Spezifikationssprache verwendet. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 35 1.2 Formale und notationelle Grundlagen Algebraische Datentypen Beispiel(algebraische Datentypen): Sei Elem eine bereits definierte Sorte. Dann definiert data type ElemList = emptyElemList () | app( first:Elem, rest:ElemList ) end die Signatur (S,F): A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 36 1.2 Formale und notationelle Grundlagen S = {ElemList} F = {emptyElemList : → ElemList app : Elem × ElemList → ElemList first : ElemList → Elem rest : ElemList → ElemList isemptyElemList : ElemList → Bool isapp : ElemList → Bool }, wobei die Sorten und Funktionen die übliche Bedeutung haben. emptyElemList und app heißen die Konstruktorfunktionen (Konstruktoren) von (S,F). A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 37 1.2 Formale und notationelle Grundlagen Abkürzend läßt sich der Datentyp ElemList auch so definieren: data type ElemList = list of Elem end A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 38 1.2 Formale und notationelle Grundlagen Definition(Syntax algebraischer Datentypen): Deklarationen algebraischer Datentypen haben die Form: data type <Sortendekl>+ end wobei <Sortendekl> ::= <Listensortendekl> | <Konstruktorsortendekl> <Listensortendekl> ::= "list of" <Sortenname> <Konstruktorsortendekl> ::= <Alternativendekl> ( "|" <Alternativendekl> )* <Alternativendekl> ::= <Konstruktorname> "(" [ Komponentendekl ("," Komponentendekl)* ] ")" Komponentendekl ::= [<Selektorname> ":"] <Sortenname> A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 39 1.2 Formale und notationelle Grundlagen Zur Semantik algebraischer Datentypen: • unterschiedliche Konstruktorterme repräsentieren unterschiedliche Werte • alle Werte der Sorten sind erzeugt • Beweisprinzip: Induktion über den Aufbau der Terme A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 40 1.2 Formale und notationelle Grundlagen 41 Bemerkung: • Algebraische Datentypdeklarationen finden sich in den meisten funktionalen Programmiersprachen und sehr vielen Spezifikationssprachen. • Wichtiges Anwendungsgebiet ist die Spezifikation der abstrakten Syntax von Programmiersprachen. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 1.2 Formale und notationelle Grundlagen 42 Abschließende Bemerkungen • Weitere Spezifikationstechniken werden im Zusammenhang mit ihrer Anwendung erläutert. • Rekursive Prädikatspezifikationen werden für operationelle Semantik und Typeigenschaften benötigt. • Ziel ist hier: eine klare Sprachebene, um Eigenschaften von Programmiersprachen und von Programmen ausdrücken zu können. • Ziel ist hier nicht: Formalisierung in allen Details. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2. Grundlagen objektorientierter Sprachen 44 2. Grundlagen objektorientierter Sprachen 2.1 Konzepte objektorientierter Programmierung 2.2 Semantik objektorientierter Sprachen 2.3 Eigenschaften objektorientierter Programme 2.4 Modularität und Kapselung A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.1 Konzepte objektorientierter Programmierung 2.1 Konzepte objektorientierter Programmierung A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 45 2.1 Konzepte objektorientierter Programmierung 2.1.1 Grundkonzepte : • Objektkonzept • Klassifikation und Subtyping 2.1.2 Sprachliche Konzepte : • Beschreibung von Objekten • Vererbungskonzept • dynamische Methodenauswahl • Zusammenwirken der Konzepte A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 46 2.1 Konzepte objektorientierter Programmierung 2.1.3 Pragmatische Aspekte : • Vererbung versus Subtyping • Objektorientierte Sicht auf Prozeduren und Klassen Ziel: • Wiederholung auf abstrakterem Niveau • Zusammenhang von Konzepten und Sprachmitteln A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 47 2.1 Konzepte objektorientierter Programmierung 2.1.1 Grundkonzepte A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 48 2.1 Konzepte objektorientierter Programmierung 49 Objektkonzept ,,The basic philosophy underlying object-oriented programming is to make the programs as far as possible reflect that part of the reality they are going to treat. It is then often easier to understand and to get an overview of what is described in programs. The reason is that human beings from the outset are used to and trained in the perception of what is going on in the real world. The closer it is possible to use this way of thinking in programming, the easier it is to write and understand programs.“ aus: Object-oriented Programming in the BETA Programming Language A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.1 Konzepte objektorientierter Programmierung 50 Objektorientiertes Paradigma: Betrachte ein Softwaresystem als eine Menge kooperierender Objekte. obj1 a1: a2: obj2 obj2 . m( 1814, "SS1999") m(p1,p2) {..} m1() {..} m2( p ) {..} A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 a: m(p1,p2) {..} n( p,r ) {..} 2.1 Konzepte objektorientierter Programmierung Objekte sind eigenständige Ausführungseinheiten mit: • • • • • Zustand Identität Lebensdauer Aufenthaltsort Verhalten Im Vergleich zur prozeduralen Programmierung: • andere Programmstruktur • anderes Ausführungsmodell A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 51 2.1 Konzepte objektorientierter Programmierung Konsequenz des Objektkonzepts: • saubere Schnittstellenbildung ◦ öffentlich zugängliche Methoden ◦ öffentlich zugängliche Attribute • Schnittstelle verbirgt Implementierung (Information Hiding) • Schnittstelle ist Basis für Verhaltensbeschreibung A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 52 2.1 Konzepte objektorientierter Programmierung Klassifikation und Subtyping Klassifikation: Klassifizieren ist eine allgemeine Technik, um Wissen über Begriffe, Dinge und deren Eigenschaften hierarchisch zu strukturieren. Das Ergebnis nennen wir eine Klassifikation. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 53 2.1 Konzepte objektorientierter Programmierung 54 Beispiel(Klassifikation der Rechtsgebiete): Recht Öffentliches Recht Bürgerliches Recht Privatrecht Kirchenrecht Handelsrecht Urheberrecht A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.1 Konzepte objektorientierter Programmierung 55 Beispiel(Klassifikation der Wirbeltiere): Wirbeltiere Fische Lurche Reptilien Säugetiere Vögel Wale Primaten Paarhufer A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.1 Konzepte objektorientierter Programmierung 56 Beispiel(Klassifikation der Figuren): Figur Ellipse Kreis Vieleck Viereck Der Pfeil % repräsentiert die ist-ein-Beziehung. Dreieck Parallelogramm Raute Rechteck Quadrat A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 Ziel: Anwendung der Klassifikationstechnik auf Software-Objekte 2.1 Konzepte objektorientierter Programmierung 57 Beobachtungen zu Klassifikationen: • • • • Sie können sich auf Objekte oder Gebiete beziehen. Sie können baumartig oder DAG-artig sein. Objektklassifikationen begründen ist-ein-Beziehungen. abstrakte Klassen vs. nicht abstrakte Klassen Prinzip der Substitution: Überall, wo Oberklassenobjekte erwartet werden, lassen sich auch Unterklassenobjekte verwenden. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.1 Konzepte objektorientierter Programmierung In der Softwaretechnik: 1. Klassifikation syntaktisch: Unterklassenobjekte haben größere Schnittstellen als Oberklassenobjekte (Auswirkung auf Programmiersprache). 2. Klassifikation semantisch: Unterklassenobjekte bieten mindestens das Verhalten, welches Oberklassenobjekte haben. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 58 2.1 Konzepte objektorientierter Programmierung Abstraktion: ... das Heraussondern des unter einem bestimmten Gesichtspunkt Wesentlichen vom Unwesentlichen. [Meyers großes Taschenlexikon] A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 59 2.1 Konzepte objektorientierter Programmierung 60 Mittels Abstraktion: • ausgehend von unterschiedlichen Objekten oder Typen mit gemeinsamen Eigenschaften • Ausarbeitung eines abstrakteren Typs, der die gemeinsamen Eigenschaften zusammenfasst • entspricht dem Verkleinern der Schnittstelle • Programme, die sich nur auf diese gemeinsamen Eigenschaften stützen, arbeiten dann für alle Objekte des abstrakteren Typs. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.1 Konzepte objektorientierter Programmierung 61 Beispiel(Abstraktion): class Student { String name; int matNr; ... void drucken() { System.out. println( name ); System.out. println( matNr ); } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 class Professor { String name; int telNr; ... void drucken() { System.out. println( name ); System.out. println( telNr ); } } 2.1 Konzepte objektorientierter Programmierung Abstraktion: • Typ Person mit Methode drucken • Algorithmus auf Basis von Person Anwendungsbeispiele: • Ein/Ausgabe-Schnittstellen • Fenstersysteme • ... A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 62 Person[] p = new Person[4]; p[0] = new Student(...); p[1] = new Professor(...); ... for(i=1; i<p.length; i++){ p[i].drucken(); } 2.1 Konzepte objektorientierter Programmierung interface Person { void drucken(); } class Student implements Person { ... } class Professor implements Person { ... } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 63 2.1 Konzepte objektorientierter Programmierung Spezialisierung: ... das Hinzufügen speziellerer Eigenschaften zu einem Gegenstand oder das Verfeinern eines Begriffs durch Einführen weiterer Merkmale. (z.B. berufliche Spezialisierung) A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 64 2.1 Konzepte objektorientierter Programmierung Mittels Spezialisierung: • ausgehend von allgemeinen Objekten bzw. Typen • Erweiterung dieser Objekte und ihrer Implementierung • entspricht dem Hinzufügen von zusätzlichen und spezielleren Eigenschaften • Voraussetzung: spezialisierte Objekte verhalten sich konform zu den allgemeineren Objekten • Programme, die auf den allgemeineren Objekten arbeiten, arbeiten dann auch korrekt auf den spezielleren Objekten. • Vererben von Implementierungsteilen,Wiederverwendung A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 65 2.1 Konzepte objektorientierter Programmierung 66 Beispiel(Spezialisierung): Spezialisierung: • Entwickle Implementierung zu Typ Person. • Spezialisiere sie. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 class Person { String name; ... void drucken(){ System.out.println(name); } } 2.1 Konzepte objektorientierter Programmierung Vererbt werden: • Attribute • Methoden Methoden können in Subklassen überschrieben werden A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 67 class Student extends Person { int matNr; ... void drucken(){ super.drucken(); System.out.println(matNr); } } class Professor extends Person { int telNr; ... void drucken(){ super.drucken(); System.out.println(telNr); } } 2.1 Konzepte objektorientierter Programmierung 2.1.2 Sprachliche Konzepte A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 68 2.1 Konzepte objektorientierter Programmierung Beschreibung von Objekten • Klassenkonzept • Prototypkonzept A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 69 2.1 Konzepte objektorientierter Programmierung 70 Klassenkonzept: • Der Programmierer beschreibt nicht einzelne Objekte, sondern deklariert Klassen. • Klasse = Beschreibung der Eigenschaften, die die Objekte dieser Klasse haben sollen • Während der Programmausführung werden Objekte zu deklarierten Klassen erzeugt (Instanzieren). • Klassen können zur Ausführungszeit nicht verändert werden. • Klassendeklaration entspricht der Deklaration von Verbundtypen imperativer Sprachen. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.1 Konzepte objektorientierter Programmierung 71 Prototypkonzept: • Der Programmierer beschreibt direkt einzelne Objekte. • Neue Objekte werden durch Klonen existierender Objekte und Verändern ihrer Eigenschaften zur Ausführungszeit erzeugt. • Klonen eines Objekts obj = Erzeugen eines neuen Objekts, das die gleichen Eigenschaften wie obj besitzt. • Verändern = dem Objekt werden weitere Attribute hinzugefügt oder Methoden werden durch andere ersetzt. • Umgesetzt beispielsweise in der Sprache Self. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.1 Konzepte objektorientierter Programmierung Vererbungskonzept Vererbung: Sprachlich unterstützte Wiederverwendungsmöglichkeit der Eigenschaften/des Codes einer anderen Klasse: • meist mit Subtyping gekoppelt • ergänzt um Spezialisierungsmöglichkeiten: ◦ Hinzufügen von Attributen, Methoden, etc. ◦ Überschreiben von Methoden ◦ Verwenden überschriebener Methoden A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 72 2.1 Konzepte objektorientierter Programmierung Ziele: • fehlerträchtiges Kopieren von Code vermeiden • Reduktion der Programmgröße • Spezialisierung von Schnittstellen, bei denen Kopieren bzw. Modifizieren nicht möglich ist A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 73 2.1 Konzepte objektorientierter Programmierung Beispiel(Vererbung): class Person { String name; int geburtsdatum; /* in der Form JJJJMMTT */ void drucken() { System.out.println("Name: "+ this.name); System.out.println("Geburtsdatum: "+ geburtsdatum); } boolean hat_geburtstag ( int datum ) { return (geburtsdatum % 10000) == (datum % 10000); } Person( String n, int gd ) { name = n; geburtsdatum = gd; } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 74 2.1 Konzepte objektorientierter Programmierung class Student extends Person { int matNr; int semester; void drucken() { super.drucken(); System.out.println( "Matrikelnr: " + matNr ); System.out.println( "Semesterzahl: " + semester ); } Student( String n, int gd, int mnr, int sem ) { super( n, gd ); matNr = mnr; semester = sem; } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 75 2.1 Konzepte objektorientierter Programmierung Dynamische Methodenauswahl Methodenauswahl: • statisch: Zur Übersetzungszeit wird jeder Aufrufstelle die auszuführende Methode zugeordnet. • dynamisch: Zur Laufzeit wird an der Aufrufstelle das Zielobjekt berechnet und in Abhängigkeit vom Zielobjekt die auszuführende Methode ausgewählt. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 76 2.1 Konzepte objektorientierter Programmierung 77 Bemerkung: Dynamische Methodenauswahl ist Voraussetzung: • für Spezialisierung • für das Arbeiten mit abstrakten Klassen und Schnittstellen A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.1 Konzepte objektorientierter Programmierung Zusammenwirken der Konzepte Beispiel(Abstract Window Toolkit): 1. Die Elemente der Bedienoberfläche werden als Objekte modelliert. 2. Oberflächenelemente werden klassifiziert: A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 78 2.1 Konzepte objektorientierter Programmierung 79 Object Component Canvas Label Button Container List Choice CheckBox Scrollbar ScrollPane Window Frame Dialog Panel TextComponent TextArea TextField FileDialog A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 Applet 2.1 Konzepte objektorientierter Programmierung 80 3. Gemeinsame Programmteile werden in der abstrakten Klasse Component zusammengefasst und von dort vererbt (über 100 Methoden). 4. Viele Klassen benutzen die Klasse Component als Abstraktion. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.1 Konzepte objektorientierter Programmierung 81 Beispiel(Verwendung von Abstraktion): public class Container extends Component { ... public Component getComponent(int n) { ... } public Component[] getComponents() { ... } public Component add(Component comp) { ... } public Component add(Component comp, int index) { ... } ... public void remove(Component comp) { ... } ... public Component getComponentAt(int x, int y) { ... } ... } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.1 Konzepte objektorientierter Programmierung 82 5. Dynamische Methodenauswahl ist Vorausssetzung für das Funktionieren der Komponentenhierarchie und für die Anbindung der zu steuernden Anwendungen über die Listener-Schnittstellen. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.1 Konzepte objektorientierter Programmierung 2.1.3 Pragmatische Aspekte A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 83 2.1 Konzepte objektorientierter Programmierung Vererbung versus Subtyping 1. Subtyping ohne Vererbung: Objektorientierte Programmierung kann auf Vererbung verzichten, aber nicht auf Subtyping und dynamische Methodenauswahl Beispiel(Delegation statt Vererbung): interface String int void boolean } Person { getName(); getGeburtsdatum(); drucken(); hat_geburtstag( int datum ); A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 84 2.1 Konzepte objektorientierter Programmierung public class PersonCl implements Person { String name; int geburtsdatum; /* in der Form JJJJMMTT */ public PersonCl( String n, int gd ) { name = n; geburtsdatum = gd; } public String getName() { return name; } public int getGeburtsdatum() { return geburtsdatum; } public void drucken() { System.out.println("Name: "+ this.name); System.out.println("Geburtsdatum: "+ this.geburtsdatum); } public boolean hat_geburtstag ( int datum ) { return (this.geburtsdatum%10000) == (datum%10000); } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 85 2.1 Konzepte objektorientierter Programmierung interface Student extends Person { public int getMatNr(); public int getSemester(); } public class StudentCl implements Student { Person personPart; int matNr; int semester; StudentCl( String n, int gd, int mnr, int sem ) { personPart = new PersonCl( n, gd ); matNr = mnr; semester = sem; } public String getName() { return personPart.getName(); } public int getGeburtsdatum() { return personPart.getGeburtsdatum(); } public void drucken() { personPart.drucken(); System.out.println( "Matrikelnr: " + matNr ); System.out.println( "Semesterzahl: " + semester ); } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 86 2.1 Konzepte objektorientierter Programmierung public boolean hat_geburtstag( int datum ) { return personPart.hat_geburtstag( datum ); } public int getMatNr() { return matNr; } public int getSemester() { return semester; } } public class Test { public static void main( String[] argv ) { int i; Person[] pf = new Person[3]; pf[0] = new PersonCl( "Meyer", 19631007 ); pf[1] = new StudentCl( "Schmidt", 19641223, 6758475, 5 ); pf[2] = new StudentCl( "Planck", 18580423, 3454545, 47 ); for( i = 0; i<3; i = i+1 ) { pf[i].drucken(); System.out.println(); } } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 87 2.1 Konzepte objektorientierter Programmierung 88 2. Vererbung ohne Subtyping: Vererbung wird teilweise zur Codewiederverwendung benutzt, ohne eine ist-ein-Beziehung zwischen Subklassenund Superklassenobjekt zu etablieren (problematisches Vorgehen). Beispiel(Vererbung ohne Subtyping): public class List { public boolean contains( int i ) { return true; } public int getFirst( ) { return 0;} public void addFirst( int i ) { } public void removeFirst() { } public void remove( int i ) { } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.1 Konzepte objektorientierter Programmierung public class Set extends List { public int getSome() { return getFirst(); } public void add( int i ) { addFirst(i); } public void addFirst( int i ) { if( !contains(i) ) super.addFirst(i); } public void removeFirst() { System.out.println("Should not be used!!"); } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 89 2.1 Konzepte objektorientierter Programmierung 90 Objektorientierte Sicht auf Prozeduren und Klassen 1. Zeiger auf Prozeduren lassen sich über Objekte realisieren: ◦ definiere Schnittstellentyp mit einer Methode, deren Signatur der Prozedursignatur entspricht ◦ für jede Prozedur p implementiere den Schnittstellentyp durch Klasse KP. ◦ statt dem Zeiger auf die Prozedur p übergebe KP-Objekt op ◦ Aufruf: op.p(...) A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.1 Konzepte objektorientierter Programmierung Beispiel(Zeiger auf Prozeduren): Call-back-Prozeduren werden im AWT durch sogenannte Listener-Objekte realisiert, die eine Listener-Schnittstelle implementieren. Besitzt diese Schnittstelle genau eine Methode, ergibt sich ein Verhalten wie bei Prozedurzeigern. public interface ActionListener extends EventListener { /** * Invoked when an action occurs. */ public void actionPerformed(ActionEvent e); } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 91 2.1 Konzepte objektorientierter Programmierung 92 2. Analogie zwischen Prozeduren und Klassen: ◦ beides sind statische Programmelemente, von denen zur Laufzeit Instanzen gebildet werden (Prozedurinkarnationen, Objekte) ◦ Prozedurinkarnationen und Objekte besitzen Identität, Zustand und Lebensdauer. ◦ Besitzt ein Objekt einen eigenen Thread und kann es sein Ende selbst bestimmen (aktives Objekt), verhält es sich wie eine Prozedurinkarnation, die während der Laufzeit von ”außen” angesprochen werden kann. BETA nutzt diese Analogie und vereinheitlicht Klassen und Methoden zu sogenannten Pattern. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.1 Konzepte objektorientierter Programmierung 3. Klassen als Objekte: Betrachtet man eine Klasse als Objekt, muss sie für ihre Aufgaben Methoden bereitstellen: ◦ Erzeugen von Objekten ◦ Auskunft über ihre Eigenschaften geben ◦ Konstruktion neuer Klassen ermöglichen Beispiel(Introspektion in Java): import java.lang.reflect.*; public class Inspektor { public static void main(String[] ss) { try{ A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 93 2.1 Konzepte objektorientierter Programmierung Class klasse = Class.forName( ss[0] ); Method[] methoden = klasse.getMethods(); for( int i = 0; i < methoden.length; i++ ){ Method m = methoden[i]; Class retType = m.getReturnType(); String methName = m.getName(); Class[] parTypes = m.getParameterTypes(); System.out.print(retType.getName()+" "+methName+"("); for( int j = 0; j < parTypes.length; j++ ){ if( j > 0 ) System.out.print(", "); System.out.print( parTypes[j].getName() ); } System.out.println( ");" ); } } catch( ClassNotFoundException e ) { System.out.println("Klasse "+ss[0]+" nicht gefunden"); } } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 94 2.2 Semantik objektorientierter Sprachen 95 2.2 Semantik objektorientierter Sprachen 2.2.1 Einführung A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen Anwendungen von Semantikspezifikationen: • Sprachspezifikation • Hilfe beim Sprachentwurf • Grundlage zum Beweis von Spracheigenschaften • Grundlage zum Beweis von Programmeigenschaften • Klärung bei der Programmierung A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 96 2.2 Semantik objektorientierter Sprachen Beispiel(Binden von Attributen): class A { int i=1; int m() { return i*100; } int n() { return i+m(); } } class B extends A { int i=10; int m() { return i*1000; } int p() { return n(); } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 97 2.2 Semantik objektorientierter Sprachen 98 public class Bindung { int m() { A[] ar = { new A(), new B() }; return ar[0].i + ar[0].m() + ar[1].i + ar[1].m(); } public static void main(String[] argv) { A a = new B(); System.out.println(new A().m()); System.out.println(a.m()); System.out.println(a.i); System.out.println(new B().p()); System.out.println(new Bindung().m()); } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 /* /* /* /* /* 1 2 3 4 5 */ */ */ */ */ 2.2 Semantik objektorientierter Sprachen Beispiel(Binden von super-Aufrufen): class C1 { void m(){ System.out.println("in C1"); } } class C2 extends C1 { void m(){ System.out.println("in C2"); super.m(); } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 99 2.2 Semantik objektorientierter Sprachen class C3 extends C2 { } class C4 extends C3 { void m(){ System.out.println("in C4"); super.m(); } } public class Super { public static void main( String argv[] ){ C1 c1 = new C4(); c1.m(); } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 100 2.2 Semantik objektorientierter Sprachen 101 2.2.2 Operationelle Semantik einer Untersprache von Java A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen Abstrakte Syntax von Java-KE Package ImportList TypeDeclList TypeDecl ClassBody MemberDecl InterfaceBody MethodSig Type = = = = | = = | = = = pack (PackId ImportList TypeDeclList) list of PackId list of TypeDecl ClassDecl (CTypeId CTypeId ITypeIdList ClassBody) InterfaceDecl (ITypeId ITypeIdList InterfaceBody) list of MemberDecl FieldDecl (Type FieldId ) MethodDecl (MethodSig Statement) list of MethodSig Sig(Type MethodId Type) booleanT () | intT () | nullT () | ct(CTypeId ) | it(ITypeId ) A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 102 2.2 Semantik objektorientierter Sprachen Statement Exp 103 = | | | | | | | | block (Type VarId Statement) fread (VarId VarId FieldId ) fwrite(VarId FieldId VarId ) cassign(VarId Type Exp) new (VarId CTypeId ) seq(Statement Statement) if (Exp Statement Statement) while(Exp Statement) catch(Statement CTypeId VarId Statement) //try − body CTypeId = NullPExc or CastExc | invoc(VarId VarId MethodId Exp) | call (VarId CTypeId MethodId Exp) = ic(Int) | bc(Bool ) | null () | id (VarId ) | unary(UnOp Exp) | binary(Exp BinOp Exp) PackId , FieldId , MethodId , CTypeId , ITypeId und VarId sind einfache Sorten. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen 104 Kontextabhängige Syntax von Java-KE Kontextbedingungen: 1. Kontextbedingungen wie in Java. Ausnahme: Die Methode main zum Starten eines Programmes hat genau einen Parameter des Typs int und liefert einen Wert des Typs int. 2. Programme haben eine Klasse Object mit mindestens einer Methode (in diesem Fall ist der zweite Parameter von ClassDecl auch Object, ohne dass dies den Schluss erlaube, Object sei ein echter Subtyp von sich selbst). A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen 105 3. Programme haben die Klassen CastExc und NullPExc. 4. Der CTypeId in einer call Anweisung muss die Superklasse bezeichnen, in der die Supermethode definiert ist. 5. Deklarierte Variablen dürfen nicht res oder exc genannt werden. res ist implizit deklariert und sichtbar in jeder Anweisung, wie auch par und this. exc darf in Anweisungen nicht verwendet werden. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen 106 6. FieldIds sind eindeutig, d.h. Attribute in verschiedenen Klassen müssen unterschiedlich benannt werden (z.B. durch Voranstellung des CTypeId der enthaltenden Klasse). 7. Typnamen müssen eindeutig sein (zum Beispiel darf ein Paket P nur Pakete importieren, deren Typnamen sich von denen, die in P deklariert sind, unterscheiden). A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen Definition(Programmbereich in Java-KE): Jedes Paket P definiert einen Programmbereich, der • P enthält sowie • alle direkt oder indirekt von P importierten Pakete. Bemerkung: Für jedes Programmelement (Deklaration, Anweisung) gibt es einen minimalen Programmbereich, in dem es enthalten ist. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 107 2.2 Semantik objektorientierter Sprachen Statische Semantik Die statische Semantik liefert die Begriffe, um die Ausführungszustände von Programmen zu beschreiben. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 108 2.2 Semantik objektorientierter Sprachen : T ype × T ype → Bool ≺ : T ype × T ype → Bool ObjId InstVar DeclMethId VirtMethId ProgPart : : : : : 109 // Subtyprelation // echte Subtyprelation eine passende Menge von Objektbezeichnern eine passende Menge von typisierten Instanzvariablen eindeutige Namen für die implementierten Methoden eindeutige Namen für die virtuellen Methoden DeclMethId | VirtMethId | Statement@ wobei Statement@ die Menge der Anweisungsknoten des kleinsten umgebenden Programmbereichs bezeichnet. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen 110 Bemerkungen: • Eine implementierte Methode ist eine Methode, deren Rumpf in einer Klasse deklariert ist. • Eine virtuelle Methode ist eine Methode, die in einem Typ deklariert ist. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen 111 Die Funktion vis : ProgPart → POW (VarId ) liefert für jeden Programmteil pp die Menge der sichtbaren VarIds. Für Anweisungen c gilt: vis(c) enthält res, exc, this, par und die deklarierten lokalen Variablen. Für Methodenabstraktionen m gilt: vis(m) = {this, par, exc} Die Funktion typs : PackId → POW (Type) liefert für jedes Paket M die Menge der Typen, die in M bekannt sind. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen Werte und Abkürzungen von Java-KE Value = | | | b(Bool ) i (Int) null () ref (CTypeId ObjId ) typ : Value → Type // Typ eines Wertes rtyp : ProgPart → Type // Ergebnistyp einer Methode oder Typ von res styp : InstVar → Type //Typ einer Instanzvariablen A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 112 2.2 Semantik objektorientierter Sprachen 113 : VarId × ProgPart → Type // deklarierter Typ einer Variablen sichtbar in ProgPart; // styp(exc,pp)=ct(Object); ansonsten nicht determiniert impl : T ype × M ethodId → DeclM ethId // impl(T,m) ist nicht determiniert, wenn T keine // Implementierung einer Methode m enthält. vm : T ype × M ethodId → V irtM ethId // vm(T,m) ist nicht determiniert, wenn m nicht in T ist body : DeclM ethId → Statement vmid : V irtM ethId → M ethodId . : V alue × F ieldId → InstV ar //v.f liefert die Instanzvariable f des Objektes v; //ansonsten nicht determiniert styp A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen 114 Modellierung des Objektspeichers h := i : Store × InstVar × Value h i : Store × CTypeId ( ) : Store × InstVar new : Store × CTypeId alive : Store × Value → Store → Store → Value → Value → Value IV1 6= IV2 ⇒ OS hIV1 := X i(IV2 ) = OS (IV2 ) OS hT i(IV ) = OS (IV ) alive(OS hIV := Y i, X ) ⇔ alive(OS , X ) alive(OS hT i, X ) ⇔ alive(OS , X ) ∨ X = new (OS , T ) alive(OS , OS (IV )) typ(new (OS , T )) = ct(T ) A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen Zustände von Variablen und Objektspeicher State : (VarId → Value) × ({$} → Store) Zustände werden üblicherweise mit S , SQ, SR bezeichnet. Die Anwendung eines Zustandes auf ein VarId v wird mit S(v) bezeichnet, auf die Speichervariable $ mit S($). Eine Änderung von S in v oder $ wird mit S[v := E] bzw. S[$ := E] bezeichnet. Sei e ein Programmausdruck der Sorte Exp. Dann bezeichnet e[S] die Auswertung von e in S. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 115 2.2 Semantik objektorientierter Sprachen Für jeden Typ wird ein initialer Wert vorausgesetzt: init : T ype → V alue typ(init(T )) T T kann ein Schnittstellentyp oder ein Basistyp sein. init(T ) kann gleich null() sein. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 116 2.2 Semantik objektorientierter Sprachen 117 Dynamische Semantik Wir beschreiben die dynamische Semantik operationell. Um nicht auf statische Methoden zurückgreifen zu müssen, wird die Programmausführung dadurch gestartet, dass ein Objekt der Startklasse Main erzeugt wird. Die Klasse Main muss eine Methode main haben, die beim Start mit dem Eingabeargument aufgerufen wird (siehe unten). Folgende Anweisungen erläutern, was beim Programmstart passiert: int input, output; Main mainvar; mainvar = new Main(); output = mainvar. main( input ); A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen 118 Die Semantik wird durch Relationen mit folgender Signatur ausgedrückt: psem : State × P rogram × int × int × State ssem : State × Statement@ × State Die Relationen werden induktiv durch Regeln (s.u.) definiert. In den Regeln schreiben wir SP : c → SQ anstatt ssem(SP, c, SQ) SP :: p(i) → r, SQ anstatt psem(SP, p, i, r, SQ) A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen 119 ssem(SP , c, SQ) bedeutet, dass die Ausführung von c mit Vorzustand SP terminiert und in den Nachzustand SQ führt. SQ(exc) = null bedeutet, dass die Ausführung von c normal beendet wurde. Andernfalls endete die Ausführung abrupt mit einer Ausnahme. Bemerkung: Die Definition von ssem setzt keine Wohltypisiertheit der Zustände voraus. Solche Eigenschaften werden ausgehend von der Definition von ssem untersucht werden. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen 120 S [this := new (S ($), Main), par := IN , exc := null , res = init(int), $ := S ($)hMaini] : body(Main@main) → SQ S :: prog(IN ) → SQ(res), SQ S (y) 6= null S : x = y.a; → S [x := S ($)(S (y).a)] Bemerkung: Der Gebrauch dieser Regel setzt voraus, dass S (exc) = null im Vorzustand, ansonsten würde eine Ausnahme, die nicht vorgekommen ist, im Nachzustand sichtbar sein. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen 121 S (y) = null S : x = y.a; → S [$ := S ($)hNullPExci, exc := new (S ($), NullPExc)] S (x ) 6= null S : x .a = e; → S [$ := S ($)hS (x ).a := e[S ]i] S (x ) = null S : x .a = e; → S [$ := S ($)hNullPExci, exc := new (S ($), NullPExc)] A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen 122 typ(e[S ]) T S : x = (T )e; → S [x := e[S ]] typ(e[S ]) 6 T S : x = (T )e; → S [$ := S ($)hCastExci, exc := new (S ($), CastExc)] true S : x = newT (); → S [x := new (S ($), T ), $ := S ($)hT i] A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen 123 S : s1 → SQ, SQ(exc) = null , SQ : s2 → SR S : s1 s2 → SR S : s1 → SQ, SQ(exc) 6= null S : s1 s2 → SQ e[S ], S : s1 → SQ S : if (e){s1 }else{s2 } → SQ A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen 124 ¬e[S ], S : s2 → SQ S : if (e){s1 }else{s2 } → SQ ¬e[S ] S : while(e){s} → S e[S ], S : s → SQ, SQ(exc) 6= null S : while(e){s} → SQ A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen 125 e[S ], S : s → SQ, SQ(exc) = null , SQ : while(e){s} → SR S : while(e){s} → SR S : s0 → SQ, SQ(exc) = null S : try{s0 }catch(T e){s1 } → SQ S : s0 → SQ, SQ(exc) 6= null , typ(SQ(exc)) 6 T S : try{s0 }catch(T e){s1 } → SQ A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen 126 S : s0 → SQ, SQ(exc) 6= null , typ(SQ(exc)) T , SQ[e := SQ(exc), exc := null ] : s1 → SR S : try{s0 }catch(T e){s1 } → SR S [v := init(T )] : s → SQ S : {T v ; s} → SQ S (y) 6= null , typ(S (y)) styp(y 0, x = y.m(e); ), DMI = impl (typ(S (y)), m), S [this := S (y), par := e[S ], res := init(rtyp(DMI ))] : body(DMI ) → SQ S : x = y.m(e); → S [x := SQ(res), $ := SQ($), exc := SQ(exc)] A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen 127 S (y) = null S : x = y.m(e); → S [$ := S ($)hNullPExci, exc := new (S ($), NullPExc)] S [par := e[S ], res := init(rtyp(impl (T , m))] : body(impl (T , m)) → SQ S : x = superT .m(e); → S [x := SQ(res), $ := SQ($), exc := SQ(exc)] A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen Ausführung mit wohltypisierten Zuständen Argumente, warum ssem nicht von vornherein für wohltypisierte Zustände definiert wurde: • Ein Zustand ist nur im Kontext einer Anweisung wohltypisiert; deswegen wird die Quantifikation über Zustände problematisch, wenn versucht wird, Zustände typkorrekt zu machen. • Regeln auf einfache Art formulieren und dann Eigenschaften über sie beweisen • Typsicherheit als Eigenschaft beweisbar A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 128 2.2 Semantik objektorientierter Sprachen 129 wts : Store → Bool wts(OS) ⇔ ALL InstVar IV : typ(OS (IV )) styp(IV ) wt, wtp, wtr : State × ProgPart → Bool wt(S, pp) ⇔ S(this) 6= null ∧ wts(S($))∧ ∧ ALL VarId V : V ∈ vis(pp) ⇒ typ(S (V )) styp(V , pp) wtp(S, pp) ⇔ wt(S, pp) ∧ S(exc) = null wtr(S, pp) ⇔ wt(S, pp) ∧ typ(S(res)) rtyp(pp) wtr wird nur für den Nachzustand von Methoden benötigt. Für eine Anweisung gilt: wtr(S, c) ⇔ wt(S, c). A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen 130 Ausgehend von ssem wird eine semantische Relation definiert, die auch für Methodenabstraktionen gilt: sem : State × ProgPart × State sem(S, c, SQ) ⇔ wtp(S , c) ∧ ssem(S , c, SQ) sem(S, T @m, SQ) ⇔ wtp(S, T @m)∧ ∧ sem(S[res := init(rtyp(T @m))], body(T @m), SQ) sem(S, T : m, SQ) ⇔ wtp(S , T : m)∧ ∧ sem(S , impl (typ(S (this)), m), SQ) A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen 2.2.3 Zur Theorie strukturell operationeller Semantikdefinitionen A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 131 2.2 Semantik objektorientierter Sprachen 132 Definition(big step SOS): Eine Semantikdefinition der oben demonstrierten Art nennt man • großschrittig, weil zu einer Regelanwendung im Allg. viele Ausführungsschritte gehören; • strukturell, weil sie der syntaktischen Programmstruktur folgt; • operationell, weil sie die Ausführung des Programms modelliert. (big step structural operational semantics, big step SOS) A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen 133 Es gibt zwei Arten, eine SOS-Spezifikation zu lesen: 1. Liste von Inferenzregeln zur Ausführung von Programmen: sem(S , pp, SQ) gilt genau dann, wenn es eine endliche Herleitung für sem(S , pp, SQ) mit den Regeln gibt. 2. Notationelle Kurzform für eine rekursive Prädikatdefinition: Spezifikation eines Prädikats als schwächsten Fixpunkt. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen Beispiel(rekursive Prädikatdefinition): typ(e[S ]) T ssem(S , x = (T )e; , S [x := e[S ]]) ssem(S , s1 , SQ), SQ(exc) = null , ssem(SQ, s2 , SR) ssem(S , s1 s2 , SR) ssem(S , c, SR) ⇔ ... ∨ ((c hat die Form x = (T )e; ) ∧ typ(e[S ]) T ∧ SR = S [x := e[S ]] ) ... ∨ ((c hat die Form s1 s2 )∧ ∃SQ : ssem(S , s1 , SQ) ∧ SQ(exc) = null ∧ ssem(SQ, s2 , SR) ) A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 134 2.2 Semantik objektorientierter Sprachen 135 Lemma: Java-KE ist deterministisch, d.h. ssem definiert eine partielle Funktion, und es gilt für alle Zustände S , SQ, SR, pp: ssem(S , pp, SQ) und ssem(S , pp, SR) impliziert SQ = SR Beweisskizze: Wir zeigen per Induktion nach n: Sind zwei Herleitungsbäume HQ, HR mit ssem(S , pp, SQ) bzw. ssem(S , pp, SR) als Wurzeln gegeben, tiefe(HQ) ≤ n und tiefe(HR) ≤ n, dann gilt HQ = HR und insbesondere SQ = SR. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen Induktionsanfang n = 1: Bei gegebenem S und pp gibt es genau eine Instanz der Regeln ohne Vorkommen von sem in dem Antecedent. Induktionsschritt n → n + 1: Seien HQ und HR zwei Herleitungsbäume mit ssem(S , pp, SQ) bzw. ssem(S , pp, SR) als Wurzeln und o.E.d.A. tiefe(HQ) = n + 1 und tiefe(HR) ≤ n + 1 . A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 136 2.2 Semantik objektorientierter Sprachen 137 Fall 1: S und pp bestimmen die letzte angewendete Regel r in HR und HQ. 1.1: Der Antecedent von r enthält maximal ein rekursives Vorkommen von ssem. Dann bestimmen S und pp den Vorzustand S0 und den Programmteil pp0 für dieses Vorkommen (in allen Regeln nur funktionale Abhängigkeiten). Nach Induktionsvoraussetzung muss dann der Nachzustand des zugehörigen Astes der beiden Herleitungen gleich sein. Damit ist aber auch SQ = SR. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen 1.2: Der Antecedent von r enthält zwei rekursive Vorkommen von ssem. Dann bestimmen S und pp den Vorzustand S0 und den Programmteil pp0 für das erste Vorkommen (in allen Regeln nur funktionale Abhängigkeiten). Nach Induktionsvoraussetzung muss dann der Nachzustand des zugehörigen Astes der beiden Herleitungen gleich sein. Damit ist der Vorzustand für das zweite rekursive Vorkommen gleich. Die Induktionsvoraussetzung liefert wieder, dass die Nachzustände gleich sind und damit auch SQ = SR. Entsprechend für weitere rekursive Vorkommen. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 138 2.2 Semantik objektorientierter Sprachen 139 Fall 2: Gemäß S und pp kommen zwei oder drei Regeln als letzte Regeln in HR und HQ in Frage. In allen diesen Regeln ist der erste Antecedent gleich und – wie unter Fall 1 erläutert – ergibt sich gemäß Induktionsvoraussetzung ein gleicher Nachzustand zum ersten Antecedenten. Entsprechend angewendet auf die weiteren Antecedenten ergibt sich, dass die letzten Regeln in HR und HQ gleich sein müssen und SR=SQ. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.2 Semantik objektorientierter Sprachen Bemerkung: Der Beweis des obigen Lemmas benutzt die Inferenzregel-Interpretation der Semantik. Bei der Fixpunktinterpretation müsste eine andere Beweistechnik verwendet werden. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 140 2.3 Eigenschaften objektorientierter Programme 2.3 Eigenschaften objektorientierter Programme A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 141 2.3 Eigenschaften objektorientierter Programme 142 Übersicht Dieser Abschnitt gibt eine kurze Übersicht über relevante Programmeigenschaften. Wir unterscheiden: • Zusicherungen • Anweisungsspezifikationen • Ablaufeigenschaften • Schnittstelleneigenschaften • weitere Eigenschaften A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.3 Eigenschaften objektorientierter Programme Definition(Programmvariable): Eine Programmvariable ist ein lokale Variable, ein Parameter, eine Instanzvariable oder eine Klassenvariable. Programmvariablen speichern Werte. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 143 2.3 Eigenschaften objektorientierter Programme 2.3.1 Zusicherungen A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 144 2.3 Eigenschaften objektorientierter Programme 145 Sei S eine Programmstelle in einem Programm P. Eine Zusicherung an der Stelle S ist eine Eigenschaft, die immer gelten soll, wenn die Ausführung S erreicht. Zusicherungen lassen sich mittels eines Ausdrucks oder einer Formel über den an S sichtbaren Größen spezifizieren. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.3 Eigenschaften objektorientierter Programme Beispiel(Zusicherungen): 1. Einfache Eigenschaft: ... C cobj = new C(...); assert cobj.x != null ; A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 146 2.3 Eigenschaften objektorientierter Programme 2. Schleifeninvariante: public static int isqrt( int x ){ int count = 0, sum = 1; while (sum <= x) { count++; sum += 2 * count + 1; assert count*count <= x && sum == (count+1)*(count+1); } return count; } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 147 2.3 Eigenschaften objektorientierter Programme 3. Komplexere Eigenschaft im Kontext vom AWT: ... Container c; Button b; ... c.remove(b); assert !EX Container cex: !EX int i: cex.getComponents()[i] == b; ... A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 148 2.3 Eigenschaften objektorientierter Programme Bemerkung: • Zusicherungen können Aussagen über den Zustand von Programmvariablen machen. • Werden Java-Ausdrücke zur Spezifikation verwendet, sollten diese seiteneffektfrei sein. • Zusicherungen können verwendet werden, um Vorbedingungen von Methoden zu beschreiben. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 149 2.3 Eigenschaften objektorientierter Programme 2.3.2 Anweisungsspezifikationen A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 150 2.3 Eigenschaften objektorientierter Programme Anweisungsspezifikationen beschreiben das Verhalten einer Anweisung, Prozedur oder Methode (im Folgenden nur für Anweisungen formuliert). Wir unterscheiden: • Vor- und Nachbedingungsspezifikationen • Ereignisspezifikation • Spezifikation von Terminierung A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 151 2.3 Eigenschaften objektorientierter Programme 152 Vor- und Nachbedingungsspezifikationen Im Unterschied zu Zusicherungen beziehen sich Vor- und Nachbedingungsspezifikationen auf zwei Zustände. Beispiel(Vor- und Nachbedingungsspezifikationen): 1. Ohne Verwendung der Variablenwerte des Vorzustands pre x>=0 && count==0 && sum == 1 while (sum <= x) { count++; sum += 2 * count + 1; } post count*count <= x && x < (count+1)*(count+1); A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.3 Eigenschaften objektorientierter Programme 2. Mit Verwendung der Variablenwerte des Vorzustands pre a == X post a == X + n; void incrA ( int n ) { a += n; } Alternativ: pre true post a == \old(a) + n; void incrA ( int n ) { a += n; } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 153 2.3 Eigenschaften objektorientierter Programme 154 Ereignisspezifikationen Ereignisspezifikationen machen Aussagen über Ereignisse, die bei der Ausführung von Anweisungen auftreten oder nicht auftreten dürfen. Typische Ereignisse beziehen sich auf: • Auftreten von Ausnahmen • Aufrufe von Methoden • Modifikation von Variablen A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.3 Eigenschaften objektorientierter Programme Beispiel(Ereignisspezifikationen): modifiable this.width, this.height ; public void resize(int width, int height) { ... } Bedeutung: Aufrufe von resize haben nur die Erlaubnis, die Instanzvariablen width, height des Zielobjektes zu ändern. Andere Instanzvariablen dürfen nicht modifiziert werden. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 155 2.3 Eigenschaften objektorientierter Programme Bemerkung: Bei Abschwächung der Bedeutung lassen sich Ereignisspezifikationen durch Vor- und Nachbedingungsspezifikationen ersetzen: pre post $(IV) = X ; $(IV) = X \/ IV = this.width \/ IV = this.height ; public void resize(int width, int height) { ... } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 156 2.3 Eigenschaften objektorientierter Programme 157 Spezifikation von Terminierung Terminierung wird meist implizit im Zusammenhang mit Vorbedingungen spezifiziert. Eine Spezifikation pre <Vorbedingung> post <Nachbedingung> <Anweisung> bedeutet dann, wenn die Vorbedingung erfüllt ist, terminiert die Anweisung. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.3 Eigenschaften objektorientierter Programme 2.3.3 Ablaufeigenschaften A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 158 2.3 Eigenschaften objektorientierter Programme 159 Die Sequenz der Zustände, die sich bei der Ausführung eines Programms ergeben, nennen wir einen Ablauf. Dabei können die Schritte von Zustand zu Zustand mehr oder weniger umfangreich sein. Ggf. kann ein Ablauf auch mit zusätzlicher Information versehen sein (z.B. Aufruf und Verlassen von Methode). Eigenschaften, die sich auf Abläufe beziehen, nennen wir Ablaufeigenschaften. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.3 Eigenschaften objektorientierter Programme Wir unterscheiden: • Invarianten • zustandsübergreifende Eigenschaften • temporale Eigenschaften A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 160 2.3 Eigenschaften objektorientierter Programme 161 Invarianten Invarianten sind Eigenschaften, die grundsätzlich in allen Zuständen gelten sollen. Beispiel(Invarianten): 1. Typinvarianten : Das Typsystem garantiert üblicherweise, dass in allen Zuständen gilt: Speichert eine Programmvariable V einen Wert W, dann ist der Typ von W ein Subtyp des (statischen) Typs von V. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.3 Eigenschaften objektorientierter Programme 2. Klasseninvarianten : Klassen können mit Klasseninvarianten annotiert werden. Die spezifizierte Eigenschaft sollte im Wesentlichen in allen Ausführungszuständen gelten, in denen das betreffende Objekt lebt. (Mögliche Ausnahmen sind z.B. Zustände innerhalb der Ausführungszustände privater Methoden.) Beispiel(Klasseninvarianten): class C { //@ invariant myobject != null; SomeObject myobject; ... } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 162 2.3 Eigenschaften objektorientierter Programme 163 3. Klassenübergreifende Invarianten : • zur Beschreibung von Objektstrukturen, die aus Objekten mehrerer Klassen bestehen • Kapselungseigenschaften, z.B. die Garantie, dass bestimmte Objekte nur von Objekten des eigenen Pakets referenziert werden A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.3 Eigenschaften objektorientierter Programme 164 Zustandsübergreifende Eigenschaften Eine zustandsübergreifende Eigenschaft (engl. history property) bezieht sich auf mehrere Zustände. Wir konzentrieren uns hier auf zustandsübergreifende Eigenschaften, die sich auf zwei Zustände beziehen (binäre Eigenschaft). Eine binäre Eigenschaft E gilt in einem Ablauf A, wenn E für alle Paare (Z1, Z2) von (sichtbaren) Zuständen aus A gilt. Zustandsübergreifende Eigenschaften sind die einfachste Form, um die Entwicklungen in Abläufen zu beschreiben. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.3 Eigenschaften objektorientierter Programme 165 Beispiel(Zustandsübergreifende Eigenschaften): 1. Eine Variable x verändert nicht ihren Wert: constraint x == \old(x) ; oder constraint if \old(x)!= null then x == \old(x) ; 2. Der Wert einer ganzzahligen Variablen nimmt nicht ab: constraint \old(x) <= x ; Bemerkungen: Die Spezifikation von zustandsübergreifenden Eigenschaften ist insbesondere wichtig, um in Klassen das Verhalten ihrer Subklassen einzuschränken. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.3 Eigenschaften objektorientierter Programme Temporale Eigenschaften Temporale Eigenschaften können sich auf beliebig viele Zustände beziehen und schließen Existenzaussagen sowie Aussagen über Teile des Ablaufs ein. Insbesondere umfassen sie: Lebendigkeitseigenschaften (etwas Gutes wird in der Zukunft passieren) Sie stellen eine Verallgemeinerung von Invarianz und zustandsübergreifenden Aussagen dar. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 166 2.3 Eigenschaften objektorientierter Programme Beispiel(Temporale Eigenschaften): 1. Zustandsbezogene temporale Eigenschaften sometimes x != null x == 0 until a == 8 2. Ereignisbezogene temporale Eigenschaften call(m) sometimes call(n) Bemerkung: Temporale Eigenschaften sind insbesondere im Zusammenhang mit parallelen Programmen wichtig. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 167 2.3 Eigenschaften objektorientierter Programme 2.3.4 Schnittstelleneigenschaften A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 168 2.3 Eigenschaften objektorientierter Programme Schnittstelleneigenschaften unterscheiden sich von anderen Programmeigenschaften dadurch, dass sie sich nicht oder nur teilweise auf die Implementierung beziehen dürfen. Schnittstelleneigenschaften benötigen Abstraktion. Beispiel(Schnittstelleneigenschaften): package org.jmlspecs.samples.stacks; //@ model import org.jmlspecs.models.*; public abstract class UnboundedStack { /*@ public model JMLObjectSequence theStack @ initially theStack != null && theStack.isEmpty(); @*/ //@ public invariant theStack != null; A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 169 2.3 Eigenschaften objektorientierter Programme /*@ public normal_behavior @ requires !theStack.isEmpty(); @ modifies theStack; @ ensures theStack.equals(\old(theStack.trailer())); @*/ public abstract void pop( ); /*@ public normal_behavior @ modifies theStack; @ ensures theStack.equals(\old(theStack.insertFront(x))); @*/ public abstract void push(Object x); /*@ public normal_behavior @ requires !theStack.isEmpty(); @ ensures \result == theStack.first(); @*/ public abstract Object top( ); } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 170 2.3 Eigenschaften objektorientierter Programme Bemerkung: Techniken zur Spezifikation der abstrakten Datenstruktur, die der Schnittstellenspezifikation zugrunde liegt: • seiteneffektfreies, auf Verständnis optimiertes Java-Programm • mathematische Modellierung • algebraische oder logische Theorie A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 171 2.3 Eigenschaften objektorientierter Programme 2.3.5 Weitere Eigenschaften A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 172 2.3 Eigenschaften objektorientierter Programme • nicht funktionale Eigenschaften: ◦ Effizienz ◦ Portabilität ◦ Skalierbarkeit ◦ ... • Kapselungseigenschaften: ◦ Kapselungsbereiche ◦ Sicherheitsgarantien A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 173 2.4 Modularität und Kapselung 2.4 Modularität und Kapselung A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 174 2.4 Modularität und Kapselung 2.4.1 Modularität und modulare Spezifikation 2.4.2 Kapselung und Schnittstellenbildung 2.4.3 Realisieren von Kapselung A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 175 2.4 Modularität und Kapselung 2.4.1 Modularität und modulare Spezifikation A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 176 2.4 Modularität und Kapselung Definition(Modulare Programmierung): Modulare Programmierung bedeutet: 1. Ergebnis der Programmierung sind Programmmodule, nicht notwendig vollständige Programme. 2. Die Implementierung eines Moduls kann andere Module benutzen: Deren Implementierung muss nicht notwendig bekannt sein. 3. Der Anwendungskontext eines Moduls ist zur Entwicklungszeit im Allg. nicht bekannt. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 177 2.4 Modularität und Kapselung 178 Definition(Modulare Spezifikation): Eine Spezifikationstechnik für modulare Programme heißt modular, wenn sie folgende Eigenschaften besitzt: 1. Sie ermöglicht es, Modulschnittstellen zu spezifizieren. 2. Die Spezifikation S(M) eines Moduls M reicht aus, um M benutzen zu können, d.h.: ◦ S(M) beschreibt die relevanten Eigenschaften von M. ◦ S(M) reicht aus, um die Korrektheit von Modulen zu zeigen, die M benutzen. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.4 Modularität und Kapselung 3. Die Korrektheit einer Modulspezifikation S(M) lässt sich ohne Kenntnis möglicher Anwendungskontexte von M zeigen, d.h. mit Kenntnis: ◦ der Spezifikationen verwendeter Module ◦ der Implementierung von M. 4. Die Korrektheit einer Modulspezifikation S(M) bleibt in jedem zulässigen Anwendungskontext erhalten. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 179 2.4 Modularität und Kapselung 180 Beispiel(Problematik modularer Spezifikationen): package somepack; public class C { protected int a = 7; //@ ensures \result >= 7; public int getA() { return a; } } Betrachte ein Modul M, das Objekte der Klasse C als Parameter bekommt: • Der Spezifikation entsprechend kann man davon ausgehen, dass getA immer eine Zahl größer oder gleich 7 liefert. • Diese Eigenschaft kann bewiesen und in Beweisen verwendet werden. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.4 Modularität und Kapselung 181 Erweiterung von C: package someotherpack; import somepack.* public class D extends C { public void decrA() { a--; } } Nun kann es passieren, dass Modul M statt eines C-Objekts ein D-Objekt erhält. Für D-Objekte gilt aber nicht die Eigenschaft von getA, und die Beweise, die sich auf die Korrektheit von M abgestützt haben, werden ungültig. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.4 Modularität und Kapselung Lösungsansatz: • klare Schnittstellenbildung und Kapselung • Spezifikation der relevanten Eigenschaften • Subtypen müssen Spezifikation der Supertypen erfüllen A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 182 2.4 Modularität und Kapselung 2.4.2 Kapselung und Schnittstellenbildung A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 183 2.4 Modularität und Kapselung 184 Information Hiding und Kapselung (engl. encapsulation) werden häufig synonym bebraucht. In unserer Begriffsbildung ist Kapselung schärfer gefasst als Information Hiding. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.4 Modularität und Kapselung 185 Definition(Information Hiding): Information Hiding ist eine Technik, um die Abhängigkeiten zwischen Klassen und Paketen zu reduzieren: • Der Anbieter stellt nur die Information (öffentlich) zur Verfügung, die für die Benutzung notwendig ist. • Der Benutzer nutzt nur die öffentliche Information. Dementsprechend bezieht sich Information Hiding vorrangig auf Software, also statische Aspekte. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.4 Modularität und Kapselung Bemerkung: • In der OO-Programmierung gibt es zwei Arten von Benutzung: ◦ Vererbungsnutzung ◦ Anwendungsnutzung • Information Hiding ermöglicht: ◦ konsistente Namensänderungen in versteckten Programmteilen (unkritisch); ◦ Veränderungen an versteckten Implementierungsteilen, soweit sie keine Auswirkungen auf die öffentliche Funktionalität haben (kritisch). A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 186 2.4 Modularität und Kapselung Beispiel(Information Hiding): public class MyList { private int leng; private LinkedList theList; public int length() { return leng; } ... } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 187 2.4 Modularität und Kapselung Definition(Kapselung): Kapselung ist eine Technik zur Strukturierung des Zustandsraumes ablaufender Programme. Ziel ist es, durch Bildung von Kapseln mit klar definierten Zugriffsschnittstellen die Daten- und Strukturkonsistenz zu gewährleisten. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 188 2.4 Modularität und Kapselung 189 Kapseln können sein: • einzelne Objekte • zusammenhängende Objekte • eine Klasse (mit allen ihren Objekten) • alle Klassen in einer Vererbungshierarchie • Pakete (mit allen Klassen und deren Objekten) • mehrere Pakete Kapselung setzt eine Festlegung der Kapselgrenzen und der Schnittstellen an den Kapselgrenzen voraus. Kapselung bezieht sich vorrangig auf dynamische Aspekte. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.4 Modularität und Kapselung Bemerkung: • Kapselung spielt vor allem eine wichtige Rolle im Zusammenhang mit Verteilung und Parallelität. • Kapselung wird mit Mitteln des Information Hiding erreicht. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 190 2.4 Modularität und Kapselung Beispiel(Kapseln und Schnittstellen): 1. Beschränkung auf privaten Zugriff garantiert keine Kapselung von Objekten: public class Privat { private int a; private Privat nachbar; public void incrA(){ a++; nachbar.a++; } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 191 2.4 Modularität und Kapselung 2. Die Java-Klasse LinkedList realisiert Kapseln mit einem LinkedList-Objekt und ggf. mehreren ListIterator-Objekten an der Schnittstelle: A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 192 2.4 Modularität und Kapselung 193 ListIterator LinkedList 0 3 ListIterator 2 Entry Entry Entry Entry Object Object Object A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.4 Modularität und Kapselung public class LinkedList extends AbstractSequentialList implements List, Cloneable, java.io.Serializable { public LinkedList(); public LinkedList(Collection c); public Object getFirst(); public Object getLast(); public Object removeFirst(); public Object removeLast(); public void addFirst(Object o); public void addLast(Object o); public boolean contains(Object o); public int size(); ... public ListIterator listIterator(int index); ... } public interface ListIterator extends Iterator { boolean hasNext(); Object next(); boolean hasPrevious(); Object previous(); int nextIndex(); int previousIndex(); void remove(); void set(Object o); void add(Object o); } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 194 2.4 Modularität und Kapselung 3. Sicherheitslücke in Java 1.1.1: public final class Class ... { ... private Identity[] signers; ... public Identity[] getSigners() { return signers; } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 195 2.4 Modularität und Kapselung 2.4.3 Realisieren von Kapselung A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 196 2.4 Modularität und Kapselung 197 Programmiersprachen stellen Sprachkonstrukte fürs Information Hiding bereit; z.B. in Java: • Zugriffsrechte: public, default, protected, private • Einschränkung der Vererbung: final • Innere Klassen Damit lässt sich Kapselung realisieren. Eine direkte Unterstützung von Kapselung durch Sprachen gibt es nicht. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.4 Modularität und Kapselung 198 Beispiel(Kapselung): Die Klasse LinkedList implementiert Kapseln (s.o.) und schließt darin die Repräsentationsobjekte vom Typ Entry ein. Sie garantiert die folgenden Eigenschaften: a) Zugriff ist nur über LinkedList- und ListIterator-Objekte möglich. b) Benutzer besitzen keine Referenz auf Entry-Objekte. c) Zwei Kapseln haben keine Objekte gemeinsam. d) Die Eigenschaften bleiben bei Vererbung erhalten. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.4 Modularität und Kapselung 199 Realisierung: • Klasse Entry und Attribute mit Entry-Referenzen sind nur privat zugreifbar. • Klasse, die die Listeniteratoren implementiert, ist privat; deshalb lassen sich Iteratoren von außen nur über eine Methode von LinkedList-Objekten erzeugen. • Methoden der Schnittstellenobjekte geben keine Referenzen auf Repräsentationsobjekte heraus. Repräsentationsobjekte können nicht von außen eingeschleust werden. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 2.4 Modularität und Kapselung Diskussion anhand der Implementierung: public class LinkedList ... { private ... Entry header = new Entry(null, null, null); private ... int size = 0; public LinkedList() { ... } ... // public methods private Entry entry(int index) { ... } public ListIterator listIterator(int index) { return new ListItr(index); } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 200 2.4 Modularität und Kapselung private class ListItr implements ListIterator { private Entry lastReturned = header; private Entry next; private int nextIndex; ... } private static class Entry { Object element; Entry next; Entry previous; ... } private Entry addBefore(Object o, Entry e) { ... } private void remove(Entry e) { ... } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 201 2.4 Modularität und Kapselung Lesen Sie zu 2.4: A. Poetzsch-Heffter: Konzepte objektorientierter Programmierung, Springer-Verlag, 2000; Abschnitt 3.3, S. 157-171. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 202 3. Techniken zum Prüfen objektorientierter Programme 204 3. Techniken zum Prüfen objektorientierter Programme 3.1 Einführung 3.1.1 Typinformation und ihre statische und dynamische Prüfung 3.1.2 Typsicherheit 3.2 Parametrische Typsysteme und Virtuelle Klassen 3.3 Typsysteme zur Strukturierung von Objektgeflechten 3.4 Erweiterte statische Prüfung A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.1 Einführung 205 3.1 Einführung A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.1 Einführung 206 Ein Typ beschreibt Eigenschaften von Werten bzw. Objekten (Beispiele: int, Object, LinkedListhAi). Typisierung bedeutet die Annotierung von Programmelementen mit Typen: • Ausdrücke: Jede Auswertung liefert einen Wert vom Typ des Ausdrucks. • Prozeduren/Methoden: Wenn die aktuellen Parameter den richtigen Typ haben, ist das Ergebnis vom Ergebnistyp. • Variablen: Eine Variable enthält nur Werte von ihrem Typ. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.1 Einführung 207 Typisierung ist eine der erfolgreichsten Anwendungen formaler Techniken. Definition(Typsystem): Das Typsystem einer Sprache S beschreibt, • welche Typen es in S gibt, • wie Programme von S typisiert werden, • welche Bedingungen typisierte Programme erfüllen müssen (Typisierungsbedingungen). Ein Programm, das die Typisierungsbedingungen erfüllt, heißt typkorrekt. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.1 Einführung 208 Definition(Typfehler): Ein Typfehler tritt auf, wenn • einer Variablen ein Wert von einem falschen Typ zugewiesen wird; • eine Operation mit aktuellen Parametern aufgerufen wird, für die sie nicht definiert ist. Ein Typisierungsfehler tritt auf, wenn ein Programm nicht typkorrekt ist. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.1 Einführung 209 Definition(typsicher): Eine Sprache heißt typsicher (engl. type-safe), wenn bei Ausführung ihrer (typkorrekten) Programme keine Typfehler auftreten. Ein Typsystem heißt sicher (engl. sound), wenn es Typsicherheit garantiert. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.1 Einführung 210 Bemerkung: • Es gibt Programme, die nicht typkorrekt sind, aber trotzdem nicht zu Typfehlern führen. • Bei einem sicheren Typsystem folgt Typsicherheit also aus Typkorrektheit. Literatur zur Typisierung: Kim B. Bruce: Foundations of Object-Oriented Languages. Types and Semantics. MIT Press, 2002. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.1 Einführung 3.1.1 Typinformation und ihre statische und dynamische Prüfung A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 211 3.1 Einführung 212 Bei Objekten liefert der Typ insbesondere die Information, welche Nachrichten ein Objekt versteht. Damit kann in einem typkorrekten Programm gewährleistet werden, dass zu jedem Methodenaufruf eine Methodenimplementierung existiert. Ziele der Typisierung in der OO-Programmierung: • Vermeidung von Typfehlern • Optimierung • Dokumentation • Abstraktion A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.1 Einführung Definition(Typprüfung): Die Typprüfung besteht aus der (statischen) Prüfung der Typisierungsbedingungen und der Prüfung von Typeigenschaften zur Laufzeit (dynamische Typprüfung). Eine typsichere Sprache heißt statisch typisiert, wenn sie keine dynamische Typprüfung vorsieht. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 213 3.1 Einführung 214 Beispiel(statisch typisiert): Java ist nicht statisch typisiert, sondern verlangt dynamische Typprüfung bei: • der Typkonvertierung (casts): Object obj = "Ich neues String-Objekt"; String str = (String) obj; ... • Feldzugriffen: String[] strfeld = new String[2]; Object[] objfeld = strfeld; objfeld[0] = new Object(); int strl = strfeld[0].length(); A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.1 Einführung 215 Typisierungsproblematik in OO-Sprachen Damit ein Subtyp-Objekt an allen Stellen vorkommen kann, an denen ein Supertyp-Objekt erwartet wird, muss garantiert sein, dass • ein Subtyp-Objekt alle Attribute seiner Supertypen hat; • ein Subtyp-Objekt alle Methoden der Supertypen hat; • die Attribute und Methoden im Subtyp zu denen in den Supertypen typmäßig passen. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.1 Einführung 216 Definition(Ko-/Kontravarianz): Sei S ein Subtyp von T. Sei TV ein Typvorkommen in T (Attribut-, Parameter-, Ergebnistyp) und SV das entsprechende Typvorkommen in S. Wir sagen: • SV und TV sind kovariant, wenn SV ein Subtyp von TV ist. • SV und TV sind kontravariant, wenn TV ein Subtyp von SV ist. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.1 Einführung 217 Fakt: OO-Sprachen lassen sich nur dann statisch typisieren, wenn: • Attributtypen ko- und kontravariant, d.h. invariant sind. • Parametertypen kontravariant sind. • Ergebnistypen kovariant sind. Beispiel(Ko-/Kontravarianz): interface C1 { String a; ... } class D1 implements C1 { Object a; ... } ... C1 cvar = new D1(); // initialisiert a String svar = cvar.a; // Typfehler: Kovarianz nicht gegeben A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.1 Einführung interface C2 { Object a; ... } class D2 implements C2 { String a; ... } ... C2 cvar = new D2(); cvar.a = new Object(); // Typfehler: Kontravarianz nicht gegeben interface C3 { String m(); ... } class D3 implements C3 { Object m(){...} ...} ... C3 cvar = new D3(); String svar = cvar.m(); // Typfehler: Kovarianz nicht gegeben A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 218 3.1 Einführung 219 interface C4 { int m(Object p); ... } class D4 implements C4 { int m(String s){ s.length();... } ... } ... C4 cvar = new D4(); cvar.m( new Object() ); // Typfehler bei Ausfuehrung von m: // Kontravarianz nicht gegeben Entsprechendes gilt für die Typen der erlaubten Ausnahmen einer Methode. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.1 Einführung Bemerkung: • Die Ko- und Kontravarianz sind grundlegend für das Verständnis von Typsystemen und konformer Subtypbildung in OO-Sprachen. • Java bietet ein recht inflexibles Typsystem: ◦ keine echt kontravarianten Parametertypen ◦ keine echt kovarianten Ergebnistypen ◦ Kovarianz nur bei den erlaubten Ausnahmen A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 220 3.1 Einführung 221 3.1.2 Typsicherheit A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.1 Einführung 222 Wir diskutieren Typsicherheit am Beispiel von Java-KE. Typsystem von Java-KE: • Typen: boolean, int, Null und ein Typ für jede deklarierte Klasse und Schnittstelle. • Subtypbeziehung wie in Java: ◦ Alle Referenztypen sind Subtyp von Object ◦ Jede Typdeklaration für T legt die direkten Supertypen von T fest. ◦ Null ist Subtyp aller Referenztypen ◦ keine Subtypbeziehung zwischen Basisdatentypen und zwischen Basisdaten- und Referenztypen. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.1 Einführung 223 • Typisierung von Java-KE: ◦ Deklarationen sind typisiert; ◦ Ausdrücke gemäß Signatur der Operatoren, der Variablendeklaration bzw. Konstanten (null bekommt den Typ Null); ◦ Anweisungsteile gemäß den Deklarationen. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.1 Einführung 224 • Typisierungsbedingungen von Java-KE: ◦ Eine überschreibende Methode hat die gleichen Parameter- und Ergebnistypen wie die überschriebene Methode. ◦ Ausdrücke in if- und while-Anweisungen müssen vom Typ boolean sein ◦ Der Zieltyp C bei einer Typkonvertierung, x=(C) e, muss ein Subtyp vom Typ von e sein. ◦ Der Typ der linken Seite einer Zuweisung, x = e, muss ein Supertyp vom Typ von e sein. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.1 Einführung 225 Lemma: Das Typsystem von Java-KE ist sicher. Für jede Anweisung pp und Zustände S, SQ gilt: wt(S , pp) ∧ ssem(S , pp, SQ) ⇒ wt(SQ, pp) wobei wt wie auf Folie 129 definiert ist. Beweisskizze: Seien S und pp beliebig. Der syntaktischen Interpretation der SOS von Java-KE zu Folge, gibt es einen Herleitungsbaum HQ von endlicher Tiefe für ssem(S , pp, SQ). A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.1 Einführung 226 Induktion nach der Herleitungstiefe n von HQ: Induktionsanfang n=1: Fallunterscheidung über die nicht-rekursiven Regeln. Wir betrachten hier exemplarisch die 1. Regel für die Zuweisung mit Typkonvertierung. Z.z.: A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.1 Einführung wt(S [x := e[S ]], pp) mit pp = (x = (T )e) ⇔ // wg. Definition von wt S [x := e[S ]](this) 6= null ∧ wts(S [x := e[S ]]($)) ∧ ∀VarId V : V ∈ vis(pp) ⇒ typ(S [x := e[S ]](V )) styp(V , pp) ⇐ // wg. x 6= this A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 227 3.1 Einführung 228 ⇐ // wg. x 6= this S (this) 6= null ∧ wts(S ($)) ∧ typ(e[S ]) styp(x , pp)∧ ∀ VarId V : V ∈ vis(pp) ⇒ typ(S (V )) styp(V , pp) ⇐ // wg. typ(e[S ]) T aus Antecedent der Regel, // und T styp(x , pp) aus Typisierungsbedingungen wt(S , pp) Entsprechend müssen alle anderen nicht-rekursiven Inferenzregeln untersucht werden. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.1 Einführung 229 Induktionsschritt n→n+1: Fallunterscheidung über die Regeln mit ssem im Antecedent. Wir betrachten hier exemplarisch die Regel für if-then-else und nehmen e[S] an. Z.z.: wt(SQ, pp) mit pp = if (e){s1 }else{s2 } ⇔ // wg. Definition von wt wt(SQ, s1 ) Und das folgt wegen ssem(S , s1 , SQ) und wt(S , s1 ) aus der Induktionsvoraussetzung. Entsprechend müssen alle anderen rekursiven Inferenzregeln untersucht werden. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.1 Einführung Literatur zu Typsicherheit von OO-Sprachen: S. Drossopoulou, S. Eisenbach: Java is Type Safe — Probably. European Conference on Object-Oriented Programming, 1997 (LNCS 1241). T. Nipkow, D. v. Oheimb: Javalight is Type-Safe — Definitely. Principles of Programming Languages, 1998. A. Igarashi, B. Pierce, P. Wadler: Featherweight Java: A Minimal Core Calculus for Java and GJ Object-Oriented Programming, Systems, Languages, and Applications, 1999. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 230 3.2 Parametrische Typsysteme und virtuelle Klassen 3.2 Parametrische Typsysteme und virtuelle Klassen A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 231 3.2 Parametrische Typsysteme und virtuelle Klassen 232 Reine Subtyp-Polymorphie bietet bei bestimmten wichtigen Anwendungssituationen keine ausreichende Unterstützung: • Parametrisierung von Typen • Spezialisieren verwendeter Typen in Subklassen Dieser Abschnitt erläutert Lösungen für diese Probleme. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.2 Parametrische Typsysteme und virtuelle Klassen 3.2.1 Parametrische Typsysteme A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 233 3.2 Parametrische Typsysteme und virtuelle Klassen Parametrisierung von Softwarekomponenten ist ein entscheidender Weg zur Wiederverwendung. Subtyp-Polymorphie leistet dazu eine wichtigen Beitrag. Nachteile: • Spezialisierung nur entlang der Subtyp-Ordnung • Keine Parametrisierung bzgl. verwendeter Typen • Keine Einschränkung der Polymorphie Moderne OO-Sprachen kombinieren Subtyp- und parametrische Polymorphie. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 234 3.2 Parametrische Typsysteme und virtuelle Klassen Wir betrachten hier GJ als Beispiel einer OO-Sprache mit parametrischem Polymorphismus. Literatur: G. Bracha, M. Odersky, D. Stoutamire, P. Wadler: Making the future safe for the past: Adding Genericity to the Java Programming Language. Object-Oriented Programming, Systems, Languages, and Applications, 1998. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 235 3.2 Parametrische Typsysteme und virtuelle Klassen 236 Parametrische/generische Typen Idee: Erlaube an Anwendungsstellen von Typen Typvariablen anstelle von Typen. Dadurch ergeben sich parametrisierte Klassen- und Schnittstellendeklarationen. Die Typvariablen müssen beim deklarierten Typ vereinbart werden. Beispiel(Typvariablen): class Pair<A,B> { A fst; B snd; Pair(A f,B s){ ... } ... } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.2 Parametrische Typsysteme und virtuelle Klassen PairhA,Bi heißt ein parametrisierter Typ. Pair heißt Typkonstruktor. Typparameter werden bei der Objekterzeugung instanziert: Pair<String,Integer> paarvar = new Pair <String,Integer> ( new String(), new Integer() ); Ein Typ wird in GJ dargestellt durch: • einen (parameterloser) Typbezeichner, • eine Typvariable, • einen Typkonstruktor angewandt auf Typen. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 237 3.2 Parametrische Typsysteme und virtuelle Klassen Beispiele: • String • A, wobei A eine deklarierte Typvariable ist. • PairhString, Objecti • PairhPairhA,Ai, Stringi Wichtigste Anwendung parametrisierter Typen sind Kollektionstypen und frei erzeugte Typen. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 238 3.2 Parametrische Typsysteme und virtuelle Klassen interface Collection<A> { public void add (A x); public Iterator<A> iterator (); } interface Iterator<A> { public A next (); public boolean hasNext (); } class NoSuchElementException extends RuntimeException {} A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 239 3.2 Parametrische Typsysteme und virtuelle Klassen class LinkedList<A> implements Collection<A> { protected class Node { A elt; Node next = null; Node (A elt) { this.elt = elt; } } protected Node head = null, tail = null; public LinkedList () {} public void add (A elt) { if (head == null) { head = new Node(elt); tail = head; } else { tail.next = new Node(elt); tail = tail.next; } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 240 3.2 Parametrische Typsysteme und virtuelle Klassen public Iterator<A> iterator () { return new Iterator<A> () { protected Node ptr = head; public boolean hasNext () { return ptr != null; } public A next () { if (ptr != null) { A elt = ptr.elt; ptr = ptr.next; return elt; } else { throw new NoSuchElementException (); } } }; } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 241 3.2 Parametrische Typsysteme und virtuelle Klassen class Test { public static void main (String[] a) { LinkedList<String> ys = new LinkedList<String>(); ys.add("zero"); ys.add("one"); String y = ys.iterator().next(); } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 242 3.2 Parametrische Typsysteme und virtuelle Klassen Parametrische/generische Methoden Analog zu Typdeklarationen lassen sich auch Methodendeklarationen parametrisieren. Beispiel(Parametrische Methoden): interface SomeCollection<A> extends Collection<A> { public A some(); } class Collections { public static <A,B> Pair<A,B> somepair(SomeCollection<A> xa, SomeCollection<B> xb ) { return new Pair<A,B>(xa.some(),xb.some()); } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 243 3.2 Parametrische Typsysteme und virtuelle Klassen 244 Einschränkung von Typvariablen Manchmal möchte man die Instanzierung von Typvariablen auf bestimmte Typen einschränken. In GJ lassen sich solche Einschränkungen (engl. bounds) bei der Deklaration der Typvariablen angeben: hA implements Ti hB extends Ti A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.2 Parametrische Typsysteme und virtuelle Klassen Beispiel(Einschränkung von Typvariablen): interface Comparable<A> { public int compareTo (A that); } class Byte implements Comparable<Byte> { private byte value; public Byte (byte value) { this.value = value; } public byte byteValue () { return value; } public int compareTo (Byte that) { return this.value - that.value; } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 245 3.2 Parametrische Typsysteme und virtuelle Klassen class Collections { public static <A implements Comparable<A>> A max (Collection<A> xs) { Iterator<A> xi = xs.iterator(); A w = xi.next(); while (xi.hasNext()) { A x = xi.next(); if (w.compareTo(x) < 0) w = x; } return w; } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 246 3.2 Parametrische Typsysteme und virtuelle Klassen 247 Bemerkung: • Trotz der relativ einfachen Erweiterung des Typsystems ergeben sich bereits recht umfangreiche Typisierungsregeln. • Generische Typen und Methoden sind kanonische Beispiele für parametrische Softwarekomponenten. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.2 Parametrische Typsysteme und virtuelle Klassen Subtyping zwischen parametrischen Typen Auch für parametrische Typen gilt: Ein Typ ist nur dann ein Subtyp eines anderen, wenn die Subtypbeziehung explizit aus den Deklarationen oder Einschränkungen ersichtlich ist: LinkedList<A> implements Collection<A> SomeCollection<A> extends Collection<A> Byte implements Comparable<Byte> <A implements Comparable<A>> A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 248 3.2 Parametrische Typsysteme und virtuelle Klassen 249 Die Subtypbeziehung zwischen Typparametern überträgt sich nicht auf parametrische Typen: LinkedList<String> LinkedList<Object> Es wird Invarianz bei den Parametern verlangt. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.2 Parametrische Typsysteme und virtuelle Klassen Beispiel(Subtypbeziehung): Folgendes Beispiel zeigt, warum Gleichheit bei aktuellen Typparametern verlangt wird: class Loophole { public static String loophole (Byte y) { LinkedList<String> xs = new LinkedList<String>(); LinkedList<Object> ys = xs; // Uebersetzungsfehler ys.add(y); return xs.iterator().next(); } } Anders als bei Feldern ist der Typ des Parameters zur Laufzeit nicht verfügbar. Deshalb ist eine dynamische Prüfung wie bei Feldern nicht möglich. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 250 3.2 Parametrische Typsysteme und virtuelle Klassen 251 Typinferenz GJ benutzt einen Typinferenzalgorithmus, der • lokal ist, d.h. der Typ eines Ausdrucks ergibt sich nur aus den Typen der Teilausdrücke; • Ausdrücke von beliebigem Referenztyp bewältigen kann (null, leere Listen); • Subsumption beherrscht, d.h. der Typ T eines Ausdrucks kann zu jedem beliebigen Supertyp verallgemeinert werden. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.2 Parametrische Typsysteme und virtuelle Klassen Beispiel(Typinferenz): class ListFactory { public <A> LinkedList<A> empty () { return new LinkedList<A>(); } public <A> LinkedList<A> singleton (A x) { LinkedList<A> xs = new LinkedList<A>(); xs.add(x); return xs; } public <A> LinkedList<A> doublet (A x, A y) { LinkedList<A> xs = new LinkedList<A>(); xs.add(x); xs.add(y); return xs; } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 252 3.2 Parametrische Typsysteme und virtuelle Klassen 253 class Test { static ListFactory f = new ListFactory(); public static void main (String[ ] args) { LinkedList<Number> zs = f.doublet(new Integer(1), new Float(1.0)); LinkedList<String> ys = f.singleton(null); LinkedList<Byte> xs = f.empty(); LinkedList<Object> err = f.doublet("abc", new Integer(1)); // compile-time error } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.2 Parametrische Typsysteme und virtuelle Klassen 254 Lösungsidee: Erweitere die Typausdrücke um den Typ ’*’, der Subtyp aller Referenztypen ist. Für ’*’ wird kovariantes Subtyping auf Parameterposition akzeptiert, d.h. z.B.: LinkedList<*> ist Subtyp von LinkedList<String> Pair<Byte,*> ist Subtyp von Pair<Byte,Byte> Für Typsicherheit ist folgende Linearitätseinschränkung erforderlich: Eine Typvariable, die mehrfach als Ergebnistyp einer Methode vorkommt, darf nicht durch ’*’ instanziert werden. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.2 Parametrische Typsysteme und virtuelle Klassen Beispiel(Typinferenz): class Cell<A> { public A value; public Cell (A v) { value = v; } public static <A> Cell<A> make (A x) { return new Cell<A>(x); } } class Pair<B,C> { public B fst; public C snd; public Pair (B x, C y) { fst = x; snd = y; } public static <D> Pair<D,D> duplicate (D x) { return new Pair<D,D>(x,x); } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 255 3.2 Parametrische Typsysteme und virtuelle Klassen class Loophole { public static String loophole (Byte y) { Pair<Cell<String>,Cell<Byte>> p = Pair.duplicate(Cell.make(null)); // compile-time error p.snd.value = y; return p.fst.value; } public static String permitted (String x) { Pair<Cell<String>,Cell<String>> p = Pair.duplicate(Cell.make((String)null)); p.fst.value = x; return p.snd.value; } } Nach Inferenz der Typen ist die Typprüfung einfach. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 256 3.2 Parametrische Typsysteme und virtuelle Klassen 257 Realisierung von Generic Java Im Wesentlichen läßt sich GJ direkt nach Java übersetzen. Die Implementierung parametrischer Typen basiert auf: • Parameterlöschung: ◦ Löschen der Typparameter ◦ Verwenden von Object anstelle von Typvariablen ◦ Einsetzen geeigneter Typkonvertierungen (casts) • Brückenmethoden ◦ Wenn eine Subklasse eine Typvariable einer Superklasse instanziert, sind ggf. zusätzliche Methoden mit geeigneter Signatur hinzuzufügen. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.2 Parametrische Typsysteme und virtuelle Klassen Beispiel(Parameterlöschung und Brückenmethoden): Durch Parameterlöschung und Einführen von Brückenmethoden erhält man zu obigem Beispiel: interface Comparable { public int compareTo (Object that); } class Byte implements Comparable { private byte value; public Byte (byte value) { this.value = value; } public byte byteValue () { return value; } public int compareTo (Byte that) { return this.value - that.value; } public int compareTo (Object that) { return this.compareTo((Byte)that); } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 258 3.2 Parametrische Typsysteme und virtuelle Klassen class Collections { public static Comparable max (Collection xs) { Iterator xi = xs.iterator(); Comparable w = (Comparable)xi.next(); while (xi.hasNext()) { Comparable x = (Comparable)xi.next(); if (w.compareTo(x) < 0) w = x; } return w; } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 259 3.2 Parametrische Typsysteme und virtuelle Klassen Probleme mit der erläuterten Technik treten auf, wenn Typparameter nur in Rückgabetypen vorkommen: class Interval implements Iterator<Integer> { private int i, n; public Interval (int l, int u) { i=l; n=u; } public boolean hasNext () { return (i <= n); } public Integer next () { return new Integer(i++); } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 260 3.2 Parametrische Typsysteme und virtuelle Klassen 261 Parameterlöschung und Einführen von Brückenmethoden liefert: class Interval implements Iterator { private int i, n; public Interval (int l, int u) { i = l; n = u; } public boolean hasNext () { return (i <= n); } public Integer next /*1*/(){ return new Integer(i++); } // bridge public Object next /*2*/(){ return next/*1*/(); } } Dies ist kein zulässiges Java-Programm, läßt sich aber im Byte-Code repräsentieren, da im Byte-Code der Ergebnistyp in die Methodensignatur mit einbezogen wird. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.2 Parametrische Typsysteme und virtuelle Klassen 3.2.2 Virtuelle Typen A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 262 3.2 Parametrische Typsysteme und virtuelle Klassen Statt Parametrisierung von Typen kann Spezialisierung verwendet werden. Dies ergibt eine andere Art Generizität: class Vector { typedef ElemType as Object; void addElement( ElemType e ) { ... } ElemType elementAt( int index ) { ... } } class PointVector extends Vector{ typedef ElemType as Point; } Man nennt ElemType hier einen virtuellen Typ. Virtuelle Typen können in Subklassen mit Subtypen überschrieben werden. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 263 3.2 Parametrische Typsysteme und virtuelle Klassen 264 Im Folgenden betrachten wir eine Erweiterung von Java um virtuelle Typen. Literatur: Kresten Krab Thorup: Genericity in Java with Virtual Types. European Conference on Object-Oriented Programming, 1997. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.2 Parametrische Typsysteme und virtuelle Klassen Aspekte virtueller Typen 1. Unterscheidung zwischen parametrischen Typen und ,,normalen“ Typen ist nicht nötig: interface IteratorOfObject { typedef A as Object; public A next (); public boolean hasNext (); } interface CollectionOfObject { typedef A as Object; typedef IteratorOfA as IteratorOfObject; public void add (A x); public IteratorOfA iterator (); } class NoSuchElementException extends RuntimeException {} A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 265 3.2 Parametrische Typsysteme und virtuelle Klassen class LinkedListOfObject implements CollectionOfObject { // erbt die virtuellen Typen A und IteratorOfA protected class Node { A elt; Node next = null; Node (A elt) { this.elt = elt; } } protected Node head = null, tail = null; public LinkedListOfObject () {} public void add (A elt) { ... /* wie auf Folie 240 */ } public IteratorOfA iterator () { return new IteratorOfA () {... /* wie auf Folie 241 */ }; } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 266 3.2 Parametrische Typsysteme und virtuelle Klassen interface IteratorOfString extends IteratorOfObject { typedef A as String; } class LinkedListOfString extends LinkedListOfObject { typedef A as String; typedef IteratorOfA as IteratorOfString; } class Test { public static void main (String[] a) { LinkedListOfString ys = new LinkedListOfString(); ys.add("zero"); ys.add("one"); String y = ys.iterator().next(); } } Jede Instanzierung benötigt eigene Typdeklaration. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 267 3.2 Parametrische Typsysteme und virtuelle Klassen 2. Subtypbeziehungen zwischen generischen Typen können deklariert werden: LinkedListOfString ist Subtyp von LinkedListOfObject; also ist folgendes Fragment typkorrekt: LinkedListOfString strl = new LinkedListOfString(); LinkedListOfObject objl = strl; objl.add( new Object() ); // VirtualTypeCastException Die durch virtuelle Typen entstehende Kovarianz bei Methodenparametern wird durch dynamische Typprüfung abgefangen. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 268 3.2 Parametrische Typsysteme und virtuelle Klassen 3. Rekursive Typen und Zusammenhang mit Vererbung lassen sich besser realisieren. a) Verschränkte Rekursion am Beispiel des Observer-Musters: interface Observer { typedef SType as Subject; typedef EventType as Object; void notify (SType subj, EventType e); } class Subject { typedef OType as Observer; typedef EventType as Object; OType observers[]; A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 269 3.2 Parametrische Typsysteme und virtuelle Klassen notifyObservers (EventType e) { int len = observers.length; for (int i=0; i<len; i++) observers[i].notify(this,e); } } interface WindowObserver extends Observer { typedef SType as WindowSubject; typedef EventType as WindowEvent; } class WindowSubject extends Subject { typedef OType as WindowObserver; typedef EventType as WindowEvent; ... } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 270 3.2 Parametrische Typsysteme und virtuelle Klassen 271 b) Rekursives Vorkommen des deklarierten Typs: Der deklarierte Typ K kann im Rumpf seiner Deklaration mit ,,This“ bezeichnet werden. Dann werden in einer Subklasse SUBK alle diese Vorkommen als Vorkommen von SUBK interpretiert und entsprechend in weiteren Subtypen: A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.2 Parametrische Typsysteme und virtuelle Klassen class SLinkedList { This tail; public This getTail() { return tail; } } class SLinkedListOfObject extends SLinkedList { typedef Elem as Object; Elem head; public Elem getHead() { return head; } } class SLinkedListOfString extends SLinkedListObject { typedef Elem as String; // SLinkedListOfString.getTail liefert SLinkedListOfString } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 272 3.2 Parametrische Typsysteme und virtuelle Klassen 273 Diskussion virtueller Typen • Vorteile: ◦ keine neue Beziehung zwischen Typen (nur ,,ist Subtyp von“, nicht ,,ist Typinstanz von“) ◦ Subtypbeziehung zwischen ,,parametrischem“ und ,,instanziertem“ Typ möglich ◦ Rekursive Abhängigkeiten lassen sich flexibler behandeln A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.2 Parametrische Typsysteme und virtuelle Klassen 274 • Nachteile: ◦ Zusätzliche dynamische Typprüfung sind nötig (vor allem wegen kovarianten Methodenparametern): . Verlust an statischer Typprüfbarkeit . Verlust an Effizienz ◦ rekursive Instanzierung (Beispiel Byte, Folie 245 A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 ) nicht unterstützt. 3.2 Parametrische Typsysteme und virtuelle Klassen 275 • Anmerkungen: ◦ Der Vorschlag von Thorup erlaubt es, virtuelle Typen als Subtypen mehrerer Typen zu deklarieren und dabei einen Klassentypen für die Erzeugung von Objekten auszuzeichnen. ◦ Es gibt mittlerweile mehrere Arbeiten, die eine Integration von parametrischen und virtuellen Typen vorschlagen, z.B.: K. Bruce, M. Odersky, P. Wadler: A statically safe alternative to virtual types. European Conference on Object-Oriented Programming, 1998. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.2 Parametrische Typsysteme und virtuelle Klassen Realisierung virtueller Typen Typsicherheit wird in der Realisierung der vorgestellten Variante durch Einführen dynamischer Prüfungen erzielt. Wir unterscheiden primäre und überschreibende Deklarationen virtueller Typen. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 276 3.2 Parametrische Typsysteme und virtuelle Klassen 277 Wesentliche Ideen: • Benutze für die Umsetzung eines virtuellen Typs T den Typ aus der primären Deklaration von T. • Definiere für jede primäre Deklaration eines virtuellen Typen T eine Cast-Methode. Diese Cast-Methode wird in Subtypen mit überschreibenden Deklarationen für T überschrieben. • Benutze die Cast-Methode zur Prüfung aktueller Parameter. • Um Typkorrektheit in den resultierenden Java-Programmen zu erreichen, füge weitere Typkonvertierungen ein (Casts). A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.2 Parametrische Typsysteme und virtuelle Klassen Beispiel(Realisierung virtueller Typen): class Vector { typedef ElemType as Object; void addElement( ElemType e ) { ... } ... } class PointVector extends Vector { typedef ElemType as Point; void addElement( ElemType e ) { ... e.getX() ... } } PointVector pv; Point pnt; ... pv.addElement(pnt); A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 278 3.2 Parametrische Typsysteme und virtuelle Klassen wird umgesetzt in class Vector { Object cast$T(Object o) { return o; } void check$addElement( Object o ) { this.addElement( this.cast$T(o) ); } void addElement( Object e ) { ... } ... } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 279 3.2 Parametrische Typsysteme und virtuelle Klassen class PointVector extends Vector { Object cast$T(Object o) { try { return (Point)o; } catch( ClassCastException c ) { throw new VirtualTypeCastException (...); } } void addElement( Object e$0 ) { Point e = (Point)e$0; ... e.getX() ... } } PointVector pv; Point pnt; ... pv.check$addElement(pnt); A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 280 3.3 Typsysteme zur Strukturierung von Objektgeflechten 3.3 Typsysteme zur Strukturierung von Objektgeflechten A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 281 3.3 Typsysteme zur Strukturierung von Objektgeflechten Typisierungstechniken kann man nicht nur zur Vermeidung (klassischer) Typfehlern nutzen, sondern auch zur Sicherstellung anderer Invarianten. Wir betrachten hier die Sicherstellung von Kapselungseigenschaften (vgl. 2.4.2 ): 3.3.1 Kapselung auf Paketebene: Confined Types 3.3.2 Andere Strukturierungsanätze A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 282 3.3 Typsysteme zur Strukturierung von Objektgeflechten 3.3.1 Kapselung auf Paketebene: Confined Types A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 283 3.3 Typsysteme zur Strukturierung von Objektgeflechten Ziel: Garantiere, dass Objekte bestimmter Typen nur vom Programmcode eines Packages manipuliert werden. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 284 3.3 Typsysteme zur Strukturierung von Objektgeflechten 285 Grundideen: 1. Betrachte jedes Paket P als Kapsel; d.h. alle Objekte (der Klassen) von P liegen in der gleichen Kapsel. Kapseln sind also disjunkt. 2. Markiere Typen, deren Objekte gekapselt werden sollen, als ,,confined“. Die Objekte dieser Typen nennen wir gekapselt. 3. Lege Regeln fest, die garantieren, dass Referenzen auf gekapselte Objekte eines Pakets P nur in Instanzvariablen, Parametern und lokalen Variablen von Objekten von P gehalten werden können. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.3 Typsysteme zur Strukturierung von Objektgeflechten outside 286 inside ungekapselt ungekapselt gekapselt A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.3 Typsysteme zur Strukturierung von Objektgeflechten 287 Zusammenhang zu Typsystemen • Jede Variable bekommt als erweiterte Typinformation: ◦ das Paket, zu dem sie gehört (implizit) • Jedes Objekt bekommt als erweiterte Typinformation: ◦ das Paket, zu dem es gehört (implizit) ◦ die Information, ob gekapselt oder nicht • Kapselungsverletzung (Typfehler) passiert, wenn eine Variable von Paket P ein gekapseltes Objekt eines anderen Pakets Q referenziert. • Es gibt statisch prüfbare Regeln, die Kapselungsverletzungen zur Laufzeit ausschließen. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.3 Typsysteme zur Strukturierung von Objektgeflechten 288 Im Folgenden betrachten wir eine Erweiterung von Java um gekapselte Typen im obigen Sinne. Literatur: B. Bokowski, J. Vitek: Confined Types. OOPSLA, 1999. Bemerkung: Das Studium der Kapselungsregelungen ist auch für die Entwicklung von OO-Programmen im Allg. hilfreich. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.3 Typsysteme zur Strukturierung von Objektgeflechten Szenarien für Export von Referenzen package inside; public class C extends outside.B { void putReferences() { C c = new C(); /*r1*/ outside.B.c1 = c; /*r2*/ outside.B.storeReference(c); /*r3*/ outside.B.c3s = new C[] {c}; /*r4*/ calledByConfined(); /*r5*/ implementedInSubclass(); /*r6*/ throw new E(); } void implementedInSubclass() { } /*r7*/ static C f = new C(); /*r8*/ static C m() { return new C(); } /*r9*/ static C[] fs = new C[]{new C()}; /*r10*/ public C() { } } public class E extends RuntimeException { } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 289 3.3 Typsysteme zur Strukturierung von Objektgeflechten Szenarien für Import von Referenzen package outside; public class B { /*r1*/ static inside.C c1; /*r2*/ static void storeReference(inside.C c2) { // store c2 } /*r3*/ static inside.C[] c3s; /*r4*/ void calledByConfined() { // store this } static void getReferences() { /*r7*/ inside.C c7 = inside.C.; /*r8*/ inside.C c8 = inside.C.m(); /*r9*/ inside.C[] c9s = inside.C.fs; /*r10*/ inside.C c10 = new inside.C(); D d = new D(); try { d.putReferences(); /*r6*/ } catch (inside.E ex) { // store ex } } } class D extends inside.C { /*r5*/ void implementedInSubclass() { // store this } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 290 3.3 Typsysteme zur Strukturierung von Objektgeflechten 291 Statische Kapselungsregeln Folgende Regeln garantieren die Kapselungsinvariante (dabei müssen Felder mit gekapseltem Elementtyp wie gekapselte Typen behandelt werden): C1: Gekapselte Typen dürfen weder public noch protected sein und nicht zum unbenannten globalen Paket gehören. C2: Subtypen von gekapselten Typen müssen auch gekapselt sein und zum gleichen Paket wie ihr Supertyp gehören. C3: Typerweiterung von gekapselten zu ungekapselten Typen ist unzulässig in Zuweisungen, Methodenaufrufen, Rückgabeanweisungen und Casts. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.3 Typsysteme zur Strukturierung von Objektgeflechten 292 Die folgenden beiden Regeln benutzen den Begriff ,,anonyme Methode“ bzw. ,,anonyme Konstruktoren“, den wir weiter unten erläutern: C4: Methoden, die auf gekapselten Objekten aufgerufen werden, müssen entweder in gekapselten Klassen deklariert oder anonyme Methoden sein. C5: Konstruktoren, die von Konstruktoren gekapselten Klassen aufgerufen werden, müssen entweder in gekapselten Klassen deklariert oder anonyme Methoden sein. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.3 Typsysteme zur Strukturierung von Objektgeflechten 293 C6: Subtypen von java.lang.Throwable und java.lang.Thread dürfen nicht als gekapselt deklariert werden. C7: Der Typ von öffentlichen und geschützten Attributen darf nicht gekapselt sein. C8: Der Ergebnistyp von öffentlichen und geschützten Methoden darf nicht gekapselt sein. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.3 Typsysteme zur Strukturierung von Objektgeflechten Definition(Anonyme Menge von Methoden): Eine Menge von Methoden kann als anonym deklariert werden, wenn sie die folgenden Bedingungen erfüllt: A1: Der implizite Parameter this wird nur benutzt, um auf Instanzvariablen zuzugreifen oder anonyme Methoden auf dem aktuellen Objekt aufzurufen. A2: Anonyme Methoden dürfen nur durch anonyme Methoden überschrieben werden. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 294 3.3 Typsysteme zur Strukturierung von Objektgeflechten 295 A3: Native Methoden dürfen nicht anonym sein. Eine Menge von Konstruktoren kann als anonym deklariert werden, wenn jeder Konstruktor der Menge nur anonyme Konstruktoren aufruft. Bemerkung: • Das Verhalten anonymer Methoden wird nur von den aktuellen Parametern und den Werten der Instanzvariablen bestimmt. • Anonyme Methoden führen nicht zu neuen Aliasen für ihren impliziten Parameter. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.3 Typsysteme zur Strukturierung von Objektgeflechten 3.3.2 Andere Strukturierungsansätze A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 296 3.3 Typsysteme zur Strukturierung von Objektgeflechten 297 In der Literatur werden weitere Strukturierungsansätze diskutiert. Wir betrachten hier: • Ballontypen: ◦ P. S. Almeida: Balloon Types: Controlling Sharing of State in Data Types. ECOOP ’97. • Besitzrelation: ◦ D. G. Clarke, J. M. Potter, J. Noble: Ownership Types for Flexible Alias Protection. OOPSLA ’98. ◦ Simple Ownership Types for Object Containment. ECOOP ’01. ◦ P. Müller, A. Poetzsch-Heffter: A Type System for Controlling Representation Exposure in Java. Formal Techniques for Java Programs ’00. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.3 Typsysteme zur Strukturierung von Objektgeflechten Verwandte Themen sind: • Vermeidung von Aliasing • Realisierung von Schreibschutz A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 298 3.3 Typsysteme zur Strukturierung von Objektgeflechten Ballontypen Ein Typ kann als Ballontyp markiert werden. Objekte von Ballontypen heißen Ballonobjekte. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 299 3.3 Typsysteme zur Strukturierung von Objektgeflechten A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 300 3.3 Typsysteme zur Strukturierung von Objektgeflechten Definition(Cluster): Sei G der Graph mit Objekten als Knoten und mit den Referenzen zwischen Nicht-Ballonobjekten und von Ballonobjekten zu Nicht-Ballonobjekten als Kanten. Ein Cluster ist ein maximaler zusammenhängender Untergraph von G. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 301 3.3 Typsysteme zur Strukturierung von Objektgeflechten 302 Definition(Interne Objekte): Ein Objekt O ist intern zu einem Ballonobjekt B genau dann, wenn gilt: • O ist ein Nicht-Ballonobjekt im gleichen Cluster wie B, oder • O ist ein Ballonobjekt, das von B oder einem Objekt im gleichen Cluster wie B referenziert wird, oder • es gibt ein zu B internes Ballonobjekt B’, zu dem O intern ist. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.3 Typsysteme zur Strukturierung von Objektgeflechten Definition(Externe Objekte): Ein Objekt O ist extern zu einem Ballonobjekt B genau dann, wenn es ungleich B ist und nicht intern zu B ist. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 303 3.3 Typsysteme zur Strukturierung von Objektgeflechten Balloninvariante: Für alle Ballonobjekte B gilt: • B wird von höchstens einer Instanzvariable referenziert. • Wenn es eine solche gespeicherte Referenz auf B gibt, kommt sie von einem zu B externen Objekt. • Kein internes Objekt zu B wird von einem Objekt referenziert, das extern zu B ist. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 304 3.3 Typsysteme zur Strukturierung von Objektgeflechten 305 Bemerkung: • Die Invariante erlaubt es, dass lokale Variablen Objekte innerhalb eines Ballons referenzieren. Deshalb kann man einen Ballon auch nicht als die Menge der von einem Ballon erreichbaren Objekte definieren. • Um die Balloninvariante zu garantieren, benutzt Almeida folgende Techniken: ◦ Zuweisung von Referenzen auf Ballonobjekte an Instanzvariablen ist im Allg. nicht erlaubt. ◦ Datenflussanalyse A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.3 Typsysteme zur Strukturierung von Objektgeflechten Besitzrelation Idee: • Führe eine Besitzrelation zwischen Objekten ein: Objekt X gehört Objekt Y. • Benutze die Besitzrelation, um Kapselung zu realisieren: Nur der Besitzer darf auf die Objekte zugreifen, die ihm gehören. • Deklariere die Besitzrelation mittels erweiterter Typinformation. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 306 3.3 Typsysteme zur Strukturierung von Objektgeflechten Beispiel(Besitzrelation): class Engine { void start() { ... } void stop() { ... } } class Driver { ... } class Car { rep Engine engine; //Teil der Representation Driver driver; // kein Teil d.Representation Car() { engine = new rep Engine(); driver = null; } rep Engine getEngine() { return engine; } void setEngine( rep Engine e){ engine=e; } void go () { if(driver!=null) engine.start(); } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 307 3.3 Typsysteme zur Strukturierung von Objektgeflechten class Main { void main() { Driver bob = new Driver(); // kein Besitzer Car car = new Car(); // kein Besitzer car.driver = bob; car.go(); car.engine.stop(); // unzulaessig car.getEngine().stop(); // unzulaessig rep Engine e = new rep Engine(); car.setEngine(e); // unzulaessig } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 308 3.3 Typsysteme zur Strukturierung von Objektgeflechten Die Besitzrelation ist eine binäre Relation zwischen Objekten. Bei der Erzeugung eines Objektes X wird festgelegt, wer Besitzer von X ist. Bei der einfachsten Variante gilt: Ein Objekt X ist • entweder global (hat also keinen Besitzer) oder • hat genau einen Besitzer. Besitzerinvariante: Alle Referenzpfade von einem globalen Objekt zu einem Objekt mit Besitzer B führen durch B. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 309 3.3 Typsysteme zur Strukturierung von Objektgeflechten 310 Bemerkung: • Die Besitzerinvariante ist tendenziell allgemeiner als die Balloninvariante. Sie erlaubt Referenzen, die den Besitzbereich verlassen. Andererseits sind bei ihr temporäre Referenzen von außen in den Besitzbereich unzulässig. • In der beschriebenen Form bringt die Besitzrelation deutliche Restriktionen mit sich. • Es gibt unterschiedliche Umsetzungen und Verfeinerungen der Besitzrelation. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.4 Erweiterte statische Prüfung 3.4 Erweiterte statische Prüfung A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 311 3.4 Erweiterte statische Prüfung Abschnitte 3.2 und 3.3 haben Erweiterungen von Typsystemen untersucht, die die Spezifikation und Prüfung von Eigenschaften erlauben, die sich mit klassischen Typsystem nicht ausdrücken lassen. Hier betrachten wir die Prüfung von Eigenschaften, die keiner gesonderten Spezifikation bedürfen, aber von der klassischen Typprüfung nicht erfasst werden. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 312 3.4 Erweiterte statische Prüfung Beispiel(Eigenschaften für ESP): Prüfe zur Übersetzungszeit, dass folgende Fehler nicht auftreten: • Dereferenzierung der Null-Referenz. • Feldzugriff mit unzulässigem Index. Während klassische und erweiterte Typsysteme meistens entscheidbar sind, zielt die erweiterte statische Prüfung auf Eigenschaften, die • in vielen Fällen entscheidbar, • aber im Allg. unentscheidbar sind. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 313 3.4 Erweiterte statische Prüfung 3.4.1 Allgemeine Aspekte der ESP A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 314 3.4 Erweiterte statische Prüfung 315 ESP ist eine junge Technik. Grundansatz: 1. Bestimme die Eigenschaften, die analysiert werden sollen. 2. Annotiere Programme soweit, dass die Prüfung automatisch durchgeführt werden kann: ◦ Schnittstelleninformation ◦ Beweisunterstützung 3. Falls die Prüfung nicht gelingt, erweitere bzw. korrigiere die Annotationen oder korrigiere das Programm und setze mit 2. fort. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 3.4 Erweiterte statische Prüfung Beispiel(Programm mit Annotationen): 1. In folgendem Programm kann es zu NullPointer-Ausnahmen kommen: class Scumble { int a; int m( Scumble s ){ return s.a; } } Die Methode m braucht s!= null als Vorbedingung. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 316 3.4 Erweiterte statische Prüfung 2. In folgendem Programm kann das Prüfwerkzeug möglicherweise nicht ableiten, dass i nach der Schleife gleich 8 ist: class MyClass { int[] a; MyClass() { int i = 1; while( i<8 ) i *= 2; if( i==8 ) a = new int[10]; a[i+1] = 324; } } Hilfreich wäre eine Annotation % //@ assert i == 8; am Schleifenende. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 317 3.4 Erweiterte statische Prüfung 3.4.2 Das Werkzeug ESC/Java A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 318 3.4 Erweiterte statische Prüfung In den letzten Jahren sind mehrere Werkzeuge für das erweiterte statische Prüfen entwickelt worden. Wir betrachten hier den Extended Static Checker for Java (ESC/Java). ESC/Java prüft (annotierte) Java-Programme auf • Abwesenheit von Laufzeitfehlern/Ausnahmen, • Konsistenz von Annotationen und Programm. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 319 3.4 Erweiterte statische Prüfung Beispiel(Konsistenz): class Consistency { static int x; //@ ensures x == 7 ; static void initX() { x = 10; } } Prüfung liefert folgendes Ergebnis: Consistency: initX() ... ------------------------------------------Consistency.java:5: Warning: Postcondition possibly not established (Post) static void initX() { x = 10; } Associated declaration is "Consistency.java", line 4, col 6: //@ ensures x == 7 ; ^ A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 320 3.4 Erweiterte statische Prüfung Technische Aspekte von ESC/Java ESC/Java basiert auf: • Spezifikationssprache JML • precondition transformation • automatischen Theorembeweisen ESC/Java ist • logisch nicht korrekt, d.h. es liefert möglicherweise unberechtigte Warnungen; • logisch nicht vollständig, d.h. es gibt Eigenschaften, die gelten, aber nicht gezeigt werden können. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 321 3.4 Erweiterte statische Prüfung 322 Die Inkorrektheit vereinfacht das Schließen, in dem nicht alle Möglichkeiten in Betracht gezogen werden. Der verwendete automatische Theorembeweiser unterstützt die Konstruktion von Gegenbeispielen. Vorführung: (ESC/Java) Siehe Vorlesung. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4. Spezifikation objektorientierter Programme 324 4. Spezifikation objektorientierter Programme 4.1 Spezifikation von Typen 4.2 Konformität von Subtypen 4.3 Module und Modularität A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 325 Zentrale Aspekte der Objektorientierung: • Information Hiding & Kapselung: ◦ Wie verhält sich ein Objekt intern? ◦ Wie wirkt ein Objekt auf die Umgebung? • Subtypbeziehung: ◦ Wann kann ich ein Objekt anstelle eines anderen verwenden? • Zusammenfassend: ◦ Was ist die Bedeutung/Semantik eines Objekts? A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 326 Beispiele: 1. Beschreibung von Verhalten bei privaten Attributen: public class WieWerdeIchBeschrieben { private int a = 0; public void set( int p ) { a = p; } public int get() { return a; } } Private Klassenkomponenten sollten nicht in einer öffentlichen Schnittstelle erscheinen. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 327 2. Nachrichten nach außen können die Umgebung ändern: public class Umweltverschmutzung { public void nurlokal( Object mo ) { do_something_good(); if( mo instanceof Atmosphere ) ((Atmosphere) mo).pollute(); } } Wie beschreibt man die Auswirkungen auf die Umgebung? Insbesondere auch die Invarianten der Umgebung. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 328 3. Für konformes Verhalten reicht Typkorrektheit nicht aus: public class Superklasse { public int a = 0; public void dincrA() { a++; } } public class Subklasse extends Superklasse { public void dincrA() { a--; } } Macht es Sinn, ein Subklasse-Objekt anstelle eines Superklasse-Objekts zu verwenden? A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 329 Lernziele: • Spezifikationstechniken • Beschreibung von Programmbausteinen A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.1 Spezifikation von Typen 4.1 Spezifikation von Typen A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 330 4.1 Spezifikation von Typen 331 Ziel: Spezifikation der Eigenschaften eines Typs unabhängig von seiner/seinen Implementierungen. Problematik: • Vokabular/Framework zur Formulierung der Eigenschaften • Information Hiding: private und geschützte Implementierungsteile können nicht in öffentlicher Spezifikation benutzt werden. • Zusammenspiel zwischen deklarativer Spezifikation und objektorientierter Implementierung A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.1 Spezifikation von Typen Ansätze: • Spezifikation mit programmiersprachlichen Mitteln • Spezifikation mit erweiterten programmiersprachlichen Mitteln unter Einhaltung der Ausführbarkeit • Spezifikation mit abstrakteren Sprachmitteln A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 332 4.1 Spezifikation von Typen Vorgehen: • Leichte Spezifikationen • Spezifikation einzelner Objekte ◦ Invarianten ◦ Verhalten von Methoden ◦ Umgebungseigenschaften • Spezifikation mit abstrakten Variablen ◦ abstrakte Variablen ◦ Abstraktionsfunktionen ◦ abstrakte Spezifikation von Umgebungseigenschaften • Spezifikation zusammenhängender Objekte A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 333 4.1 Spezifikation von Typen Dabei betrachten wir als Sprachmittel ausführbare Spezifikationen in JML , der Java Modelling Language. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 334 4.1 Spezifikation von Typen 335 4.1.1 Leichte Spezifikationen A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.1 Spezifikation von Typen 336 Definition(leichte Spezifikation): Eine leichte Spezifikation beschreibt wichtige Eigenschaften von Objekten und Methoden, die ohne großen Aufwand beschrieben werden können. Leichte Spezifikationen streben nicht die vollständige Beschreibung relevanten Verhaltens an. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.1 Spezifikation von Typen Beispiel(leichte Spezifikation): public class Bag { //@ requires input != null; public Bag(int[] input) { n = input.length; a = new int[n]; System.arraycopy(input, 0, a, 0, n); } ... } Leichte Spezifikationen sind insbesondere ein präzises Dokumentationsmittel. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 337 4.1 Spezifikation von Typen 338 Bemerkung: Leichte Spezifikationen können auch zur Annotation privater Programmelemente verwendet werden, um die Lesbarkeit oder Prüfbarkeit von Programmen zu verbessern. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.1 Spezifikation von Typen Beispiel(leichte Spezifikation): public final class String implements ... { /** Used for character storage. */ //@ private invariant value != null ; private char value[]; /** First index of the storage used. */ //@ private invariant offset >= 0 private int offset; /** Number of characters in the String */ //@ private invariant count >= 0 ; private int count; /*@ private invariant @ offset + count <= value.length ; @*/ } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 339 4.1 Spezifikation von Typen 4.1.2 Spezifikation einzelner Objekte A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 340 4.1 Spezifikation von Typen 341 Vereinfachende Annahmen: • Spezifikationen hängen nur von dem Zustand jeweils eines Objekts ab. • Relevante Attribute sind dem Anwender bekannt. • Methoden ändern nur das Objekt, auf dem sie aufgerufen wurden. • Kein Subtyping, keine Vererbung. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.1 Spezifikation von Typen Spezifiziere: • die möglichen Zustände der Objekte durch Invarianten über den Attributwerten; • das Verhalten von Methoden durch die von ihnen vorgenommenen Änderungen der Attributwerte; • Umgebungseigenschaften durch Angabe, welche Attributwerte von Methoden nicht verändert werden. Beispiel: public class Point { /** Koordinaten */ /*@ spec_public @*/ private float x, y; private float dist; } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 342 4.1 Spezifikation von Typen 343 Invarianten Invarianten spezifizieren die Bereichseinschränkungen für die Attributwerte und die Beziehungen zwischen Attributwerten. Beispiel(Invarianten): public class Point { ... //@ public invariant x >= 0.0 && y >= 0.0 ; /*@ private invariant @ dist == Math.sqrt(x*x+y*y) ; @*/ ... } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.1 Spezifikation von Typen 344 Verhalten von Konstruktoren und Methoden Konstruktoren müssen die Invarianten etablieren. Methoden müssen die Invarianten erhalten. Die Vorbedingung für ihre Anwendung und ihr Einfluss auf die Attributwerte ihres Zielobjektes und ihr Ergebnis werden mit Vor- und Nachbedingungen spezifiziert. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.1 Spezifikation von Typen 345 Beispiel(Verhalten von Konstruktoren und Methoden): public class Point { ... /** Erzeugen eines Punktes */ /*@ public normal_behavior @ requires x >= 0.0 && y >= 0.0 ; @ ensures this.x == x && this.y == y; @ also @ public exceptional_behavior @ requires x < 0.0 || y < 0.0 ; @ signals (IllegalArgumentException) @*/ public Point ( float x, float y ) { ... } /*@ public normal_behavior @ ensures \result == this.x ; @*/ public float getX() { ... } ... A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.1 Spezifikation von Typen /** Entfernung vom Ursprung */ /*@ public normal_behavior @ ensures \result == Math.sqrt(x*x+y*y); @*/ public float distance() { ... } /** Verschieben dieses Punktes */ /*@ public normal_behavior @ requires x+dx >= 0.0 && y+dy >= 0.0 ; @ ensures x == \old(x)+dx && y == \old(y)+dy ; @ also @ public exceptional_behavior @ requires x+dx < 0.0 || y+dy < 0.0 ; @ signals (IllegalArgumentException) @*/ public void move( float dx, float dy ){ ...} } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 346 4.1 Spezifikation von Typen 347 Bemerkung: • Vor- und Nachbedingungsspezifikationen liefern keine vollständige Aussage darüber, was sich verändert (z.B. bzgl. Attribut dist bei Ausführung von move). • Sie liefern keine Aussage darüber, was sich nicht verändert. • Obiges Beispiel ist keine zulässige JML-Spezifikation (s.u.). A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.1 Spezifikation von Typen 348 Spezifikation von Umgebungseigenschaften Veränderungsspezifikationen beschreiben, welche Variablen ihren Wert bei Ausführung einer Methode nicht verändern, indem sie die Variablen spezifizieren, die sich ändern dürfen. Grund für indirektes Vorgehen: • Es gibt weniger Variablen, die sich ändern. • Im Allg. sind nicht alle Variablen des Programms bekannt. Damit ist insbesondere spezifiziert, welche Eigenschaften der Umgebung (engl. frame properties) erhalten bleiben. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.1 Spezifikation von Typen Spezifikationsmittel: Veränderungsklausel (modifies, assignable clause): assignable hListe bedingter Variablenausdrückei Bespiele: assignable x, this.a, p.a.b assignable x, this.a if(this==p) Bemerkung: Schlüsselwort modifies ist gleichbedeutend mit assignable. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 349 4.1 Spezifikation von Typen 350 Bedeutung: Eine Methode bzw. ein Konstruktor darf nur an Instanzoder Klassenvariable zuweisen, die • in der Veränderungsklausel genannt ist und deren zugehörige Bedingung im Vorzustand erfüllt ist oder • die erst während der Methodenausführung allokiert werden. Fehlt eine Veränderungsklausel, hat es die gleiche Bedeutung wie eine Veränderungsklausel mit leerer Liste. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.1 Spezifikation von Typen Beispiel(Spezifikationsmittel): public class Point { ... /*@ public normal_behavior @ requires x >= 0.0 && y >= 0.0 ; @ assignable this.x, this.y ; @ ensures this.x == x && this.y == y; @ also @ private normal_behavior @ requires x >= 0.0 && y >= 0.0 ; @ assignable dist ; @ also @ public exceptional_behavior @ requires x < 0.0 || y < 0.0 ; @ signals (IllegalArgumentException) @*/ public Point ( float x, float y ) { ...} ... A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 351 4.1 Spezifikation von Typen /*@ public normal_behavior @ ensures \result == this.x ; @*/ public float getX() { ... } ... /*@ public normal_behavior @ requires x+dx >= 0.0 && y+dy >= 0.0; @ assignable x, y ; @ ensures x == \old(x)+dx && y == \old(y)+dy ; @ private normal_behavior @ requires x+dx >= 0.0 && y+dy >= 0.0; @ assignable dist ; @ also @ public exceptional_behavior @ requires x+dx < 0.0 || y+dy < 0.0 ; @ signals (IllegalArgumentException) @*/ public void move( float dx, float dy ){ ...} } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 352 4.1 Spezifikation von Typen 353 Bemerkung: Private Veränderungsklauseln sind unwichtig für die Dokumentation der öffentlichen Schnittstelle, aber nötig für das Prüfwerkzeug. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.1 Spezifikation von Typen 4.1.3 Spezifikation mit abstrakten Variablen A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 354 4.1 Spezifikation von Typen 355 Abstrakte Variablen/Attribute werden zur Spezifikation benötigt, wenn • konkrete Attribute nicht deklariert sind oder • die Zugreifbarkeitsregeln die Verwendung konkreter Attribute in Spezifikationen nicht gestatten oder • Austauschbarkeit der Implementierung erreicht werden soll. Syntax: Abstrakte Variablen/Attribute werden in JML wie Attribute, allerdings mit dem zusätzlichen Schlüsselwort model als Modifikator vereinbart. In JML haben abstrakte Attribute einen Java-Typ. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.1 Spezifikation von Typen Bedeutung: Abstrakte Attribute hängen von konkreten Attributen oder anderen abstrakten Attributen ab. Die Abhängigkeit muss in einer Abhängigkeitsklausel spezifiziert werden. Der Wert von einem abstrakten Attribut a eines Objekts ergibt sich aus den Werten der Attribute, von denen a abhängt. Wie er sich berechnet, wird in einer Repräsentationsklausel festgelegt. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 356 4.1 Spezifikation von Typen Beispiel(Abstrakte Attribute): public class Point { /** Koordinaten */ //@ public model float x, y; //@ public invariant x >= 0.0 && y >= 0.0; private double dist, angle ; /*@ private invariant dist >= 0.0 @ && 0.0 <= angle && angle <= Math.PI/2; @*/ //@ private depends x <- dist, angle ; //@ private depends y <- dist, angle ; /*@ private represents x @ <- Math.cos(angle)*dist ; @*/ /*@ private represents y @ <- Math.sin(angle)*dist ; @*/ } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 357 4.1 Spezifikation von Typen 358 Abstrakte Variablen und Umgebungseigenschaften Das Recht, eine abstrakte Variable a verändern zu dürfen, impliziert das Recht, alle konkreten Variablen zu ändern, von denen a abhängt. Beispiel(Abstrakte Variablen und Umgebungseigenschaften Der Konstruktor Point darf dist und angle verändern: A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.1 Spezifikation von Typen public class Point { /*@ public normal_behavior @ requires x >= 0.0 && y >= 0.0 ; @ assignable this.x, this.y ; @ ensures this.x == x && this.y == y; @ also @ public exceptional_behavior @ requires x < 0.0 || y < 0.0 ; @ signals (IllegalArgumentException) @*/ public Point ( float x, float y ) { ...} /*@ public normal_behavior @ ensures \result == this.x ; @*/ A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 359 4.1 Spezifikation von Typen public float getX() { ... } /*@ public normal_behavior @ requires x+dx >= 0.0 && y+dy >= 0.0; @ assignable x, y ; @ ensures x == \old(x)+dx && y == \old(y)+dy ; @ also @ public exceptional_behavior @ requires x+dx < 0.0 || y+dy < 0.0 ; @ signals (IllegalArgumentException) @*/ public void move( float dx, float dy ){ ...} } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 360 4.1 Spezifikation von Typen 361 Bemerkung: • Die Spezifikation der Veränderung von dist ist nicht mehr nötig, da x und y von dist abhängen. • Die Gleichheit in den Zusicherungsklauseln ist problematisch. Mit dem folgendem Beispiel demonstrieren wir die Spezifikation eines Typs, für den keine Attribute deklariert sind. Die Spezifikation benutzt einen komplexeren, in JML vordefinierten seiteneffektfreien Typ JMLObjectSequence: A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.1 Spezifikation von Typen Beispiel(abstrakte Variablen): //@ model import org.jmlspecs.models.*; public abstract class UnboundedStack { /*@ public model JMLObjectSequence theStack @ initially theStack != null && theStack.isEmpty(); @*/ //@ public invariant theStack != null; /*@ public normal_behavior @ requires !theStack.isEmpty(); @ assignable theStack; @ ensures theStack.equals( \old(theStack.trailer())); @*/ public abstract void pop( ); A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 362 4.1 Spezifikation von Typen /*@ public normal_behavior @ assignable theStack; @ ensures theStack.equals( \old(theStack.insertFront(x))); @*/ public abstract void push(Object x); /*@ public normal_behavior @ requires !theStack.isEmpty(); @ ensures \result == theStack.first(); @*/ public abstract Object top( ); } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 363 4.1 Spezifikation von Typen package org.jmlspecs.models; public /*@ pure @*/ class JMLObjectSequence implements JMLCollection { public /*@ pure @*/ boolean equals(Object obj) { ... } public /*@ pure @*/ boolean isEmpty() { ... } public /*@ pure @*/ Object first() throws JMLSequenceException { ... } public /*@ pure @*/ /*@ non_null @*/ JMLObjectSequence insertFront(Object item) throws IllegalStateException { ... } ... } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 364 4.1 Spezifikation von Typen 365 Methoden sind pure, wenn sie keine Seiteneffekte haben (assignable \nothing). Konstruktoren sind pure, wenn sie nur Objektattribute verändern und ansonsten keine Seiteneffekte haben. Klassen- und Schnittstellentypen, die als pure deklariert sind, dürfen nur pure Methoden und Konstruktoren haben. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.1 Spezifikation von Typen 4.1.4 Spezifikation zusammenhängender Objekte A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 366 4.1 Spezifikation von Typen 367 Objekte erbringen ihre Funktionalität meist im Zusammenwirken mit anderen Objekten. Wir betrachten hier die Fälle: • Aggregation • Komposition • Rekursive Objektbeziehungen Aggregation: Aggregation modelliert eine hat-ein-Beziehung zwischen einem Objekt X und Teilen von X. Teilobjekte sind üblicherweise außerhalb von X bekannt und zugreifbar; sie können auch Teil anderer Objekte sein. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.1 Spezifikation von Typen Bemerkung: Die Zugreifbarkeit von außen ist problematisch, da Invarianten des aggregierenden Objekts verletzt werden können. Beispiel(Aggregation): public class public /*@ //@ public //@ public public /*@ public int Line { non_null @*/ Point left,right; invariant left != right ; invariant left.x <= right.x ; non_null @*/ Color linecolor; linebreadth ; A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 368 4.1 Spezifikation von Typen /*@ public normal_behavior @ requires p1 != null && p2 != null && @ p1 != p2; @ assignable left, right; @ ensures ((left == p1 && right == p2) @ || (left == p2 && right == p1)) @ && linebreadth == 1 && ... ; @*/ public Line( Point p1, Point p2 ) { ...} ... /*@ public normal_behavior @ ensures \result == left ; @*/ public Point getLeft() { ... } /*@ public normal_behavior @ ensures \result == ... ; @*/ public float length() { ... } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 369 4.1 Spezifikation von Typen Beachte: • Ein Punkt kann zu mehreren Linien gehören. • Die zweite Invariante kann durch Verschieben eines der Endpunkte verletzt werden. Deshalb besser: A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 370 4.1 Spezifikation von Typen public class Line { public /*@ non_null @*/ Point from,to; //@ public invariant from != to ; public /*@ non_null @*/ Color linecolor; public int linebreadth ; /*@ public normal_behavior @ requires p1! = null && p2 != null && @ p1 != p2 @ assignable from, to; @ ensures form == p1 && to == p2 @ && linebreadth == 1 && ... @ ... @*/ public Line( Point p1, Point p2 ) { ...} ... /*@ public normal_behavior @ ensures \result == @ from.x <= to.x ? from : to ; @*/ public Point getLeft() { ... } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 371 4.1 Spezifikation von Typen 372 Komposition: Komposition modelliert eine exklusive Besitzerbeziehung zwischen einem Objekt X und seinen Teilen. Teilobjekte sind üblicherweise außerhalb von X bekannt und eingeschränkt zugreifbar; sie dürfen nicht direkter Teil mehrerer Objekte sein. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.1 Spezifikation von Typen 373 Beispiel(Komposition): Wir betrachten eine Klasse für Streckenzüge, die aus Linien zusammengesetzt sind. Die Streckenzüge lassen sich von außen durch Verschieben der Punkte verändern. Polyline Line from: to: Point A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 Line from: to: Point Point 4.1 Spezifikation von Typen Um zu verhindern, dass ein Line-Objekt Teil zweier Polylinien wird, kann man: • die Linien kapseln; • den Einbau von Linien kontrollieren. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 374 4.1 Spezifikation von Typen Rekursive Objektbeziehungen: Die Eigenschaften und Invarianten rekursiver Klassen erstrecken sich oft über eine unbegrenzte Anzahl von Objekten. Für ihre Spezifikation benötigt man meist Hilfsfunktionen. Beispiel(rekursive Klasse): public class IntList { private int elem; private IntList next; //@ private invariant !this.reach(next) ; /*@ pure @*/ boolean reach( IntList il ){ // aber wie beschreibe ich reach } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 375 4.1 Spezifikation von Typen Bemerkung: Obiges Beispiel zeigt: • Schranken der Ausdruckskraft der demonstrierten Spezifikationstechnik; • Problematik der Verwendung partieller Prädikate. • Eine allgemeine Spezifikationsmethode für rekursive Klassen fehlt noch. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 376 4.2 Konformität von Subtypen 4.2 Konformität von Subtypen A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 377 4.2 Konformität von Subtypen 378 Die Subtypbeziehung wird in Programmiersprachen meist rein syntaktisch verstanden: Was muss ein Subtypobjekt für Eigenschaften haben, damit an Stellen, an denen Supertypobjekte erwartet werden, • keine Typfehlern auftreten und • zu Methodenaufrufen immer geeignete Methodenimplementierungen existieren. Was bedeutet es semantisch, ein Subtyp zu sein? A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.2 Konformität von Subtypen 379 Probleme: Der Subtyp soll eine Spezialisierung des Supertyps sein. Auf Basis der operationellen Semantik lässt sich Spezialisierung nur schwer definieren: • Spezialisierung von abstrakten Typen • Erweiterung des Zustandsraums A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.2 Konformität von Subtypen Ansatz: Definiere die Subtypbeziehungen auf Basis der Spezifikationen: • Jedes Subtypobjekt muss die Spezifikationen der Supertypen erfüllen. • Für Vor- und Nachbedingungsspezifikationen: ◦ pre[Supertyp] => pre[Subtyp] ◦ post[Subtyp] => post[Supertyp] A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 380 4.2 Konformität von Subtypen Vorgehen: • Vor- und Nachbedingungsspezifikation ohne abstrakte Variablen und Abstraktion • Vor- und Nachbedingungsspezifikation mit abstrakten Variablen und Abstraktion • Behandlung von Invarianten • Umgebungseigenschaften • Zusammenwirken der Techniken A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 381 4.2 Konformität von Subtypen 382 Konkrete Pre-Post-Spezifikationen Jede Vor- bzw. Nachbedingung einer Methode im Supertyp kann als Vor- bzw. Nachbedingung der entsprechenden Methode des Subtyps interpretiert werden: • Vererben der Spezifikation A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.2 Konformität von Subtypen 383 Subtypen ohne zusätzliche Attribute: • Ererbte Methode: ◦ Spezifikation wird geerbt und nicht erweitert. • Überschreibende Methode: ◦ Vorbedingung wird geerbt und nicht erweitert. ◦ Nachbedingung wird geerbt und ggf. um Konjunkte erweitert, d.h. verstärkt. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.2 Konformität von Subtypen Beispiel(Konkrete Pre-Post-Spezifikationen): class C { /*@ public normal_behavior @ requires P @ ensures Q ; @*/ C m(){ ... } } class D extends C { /*@ also @ public normal_behavior @ requires P @ ensures \result instanceof D ; @*/ C m(){ ... } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 384 4.2 Konformität von Subtypen Zusätzliche Methode: • keine Einschränkungen an Verhalten. Bemerkung: Entsprechende Spezifikationsverfeinerungen, wie hier fürs normale Verhalten gelten fürs Ausnahmeverhalten. Subtypen mit zusätzlichen Attributen: Ist der Zustandsraum im Subtyp erweitert, kann neben den oben erläuterten Spezifikationsverfeinerungen eine Ergänzung der Vorbedingung bei überschreibenden Methoden sinnvoll sein. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 385 4.2 Konformität von Subtypen 386 Es reicht zu verlangen, dass: • pre[Supertyp] && this instanceof Subtyp ⇒pre[Subtyp] • post[Subtyp] && this instanceof Subtyp ⇒ post[Supertyp] Bemerkung: • Diese Abschwächung setzt normalerweise voraus, dass ein Supertyp seine Subtypen kennt. • Die Problematik des erweiterten Zustandsraums ist ein wichtiges Argument für die Verwendung von Abstraktion. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.2 Konformität von Subtypen Abstrakte Pre-Post-Spezifikationen Die Repräsentation der abstrakten Attribute durch die konkreten kann von Subtyp zu Subtyp verschieden sein. Die abstrakt formulierten Bedingungen werden dementsprechend unterschiedlich konkretisiert. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 387 4.2 Konformität von Subtypen Beispiel(Abstrakte Pre-Post-Spezifikationen): class C { //@ public model boolean valid; //@ public model AS state; /*@ public normal_behavior @ requires valid && r(state) @ ensures q(state) ; @*/ void m(){ ... } } class D extends C { private BD d; //@ private depends valid <- d; //@ private represents valid <- CD.pd(d) ; //@ private depends state <- d; //@ private represents state <- CD.f(d) ; ... } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 388 4.2 Konformität von Subtypen class E extends C { private BE e; //@ private depends valid <//@ private represents valid //@ private depends state <//@ private represents state ... } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 389 e; <- CE.pe(e) ; e; <- CE.g(e) ; 4.2 Konformität von Subtypen Bemerkung: • Häufig geht man auch von einer expliziten Abstraktionsfunktion aus, die den Zustandsraum des Subtyps in den des Supertyps abstrahiert. • Die abstrakten Variablen ermöglichen zwei Arten der ,,Zustandserweiterung“: ◦ Überschreiben der Repräsentationsfunktion ◦ zusätzliche abstrakte Variablen • Die Variationen in den Subtypen können von der Repräsentationsfunktion aufgefangen werden. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 390 4.2 Konformität von Subtypen 391 Behandlung von Invarianten Im Prinzip lassen sich Invarianten durch Vor- und Nachbedingungen ausdrücken. Spezifikationstechnisch sind sie aber wichtig, da sie es ermöglichen, in Supertypen das Verhalten zusätzlicher Subtypmethoden einzuschränken: • Invarianten von Supertypen müssen auch von zusätzlichen Subtypmethoden erfüllt werden. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.2 Konformität von Subtypen Beispiel(Behandlung von Invarianten): class C { public int a = 0; //@ public invariant a >= 0; ... // keine Deklaration von m } class D extends C { ... void m(){ a = -1; } // verletzt Invariante ... } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 392 4.2 Konformität von Subtypen 393 Ansatz: • Invarianten des Supertyps müssen auch von den Subtypen erfüllt werden: inv[Subtyp]⇒ inv[Supertyp] • Vorgehen: Vererbe Invarianten an Subtypen und bilde die Konjunktion mit den in den Subtypen angegebenen Invarianten. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.2 Konformität von Subtypen Problematik: Die Invarianten gelten normalerweise nicht in jedem Zustand. Was ist ihre präzise Bedeutung? Was muss im Vorzustand eines Methodenaufrufs gelten? Für die Bedeutung von Invarianten in Spezifikationssprachen gibt es bisher keine einheitliche Lösung. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 394 4.2 Konformität von Subtypen 395 Semantische Varianten: 1. Invariante über eingeschränkte Zustandsmenge: JML: ,,Invariants have to hold in each state outside of a public method’s execution and at the beginning and end of such execution.“ 2. Transformation in Vor- und Nachbedingungen: Verifikationsansätze: Invarianten müssen im Nachzustand von Konstruktoraufrufen gelten. Wenn sie im Vorzustand von Methoden gelten, müssen sie auch im Nachzustand gelten. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.2 Konformität von Subtypen 396 Für die Verifikation bringen beide Varianten ein Problem mit sich: Sei S ein Subtyp von T, m eine Methode in T und x eine Variable vom Typ T: ... x.m(...) ... In der Verifikation wird man inv[T] als Vorbedingung voraussetzen. Als Vorbedingung der Methoden von S benötigt man aber im Allg. inv[S]. Ziel ist aber die Verifikation ohne Kenntnis aller Subtypen. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.2 Konformität von Subtypen Beispiel(Behandlung von Invarianten): class C { public int a = 0; //@ public invariant a => 0; ... void m(){ ... // erhaelt die Invariante } } class Foo { void mfoo() { ... x.m() ... } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 397 4.2 Konformität von Subtypen class D extends C { public int b = 0; //@ public invariant a > 1; //@ public invariant b => 0; ... void m(){ b = 4 / a ; ... // erhaelt beide Invarianten } } Problem: Lege die Vorbedingung für den Aufruf von x.m() fest. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 398 4.2 Konformität von Subtypen Lösungsansatz: Betrachte als Invariante eines Typs T die folgende Formel INV[T]: ∀ Typen S: S ≤ T ⇒ ( typ(this)=S ⇒ inv[S] ) wobei inv[S] bei gegebenem S die Invariante der Implementierung von S bezeichnet. Dann gilt für einen Subtyp U von T: typ(this) = U ⇒ ( inv[U] ⇔ INV[T] ) A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 399 4.2 Konformität von Subtypen Bemerkung: Darüber hinaus muss man im Allg. auch zeigen, dass eine Methode von T die Invarianten aller anderen Typen unverändert lässt. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 400 4.2 Konformität von Subtypen 401 Umgebungseigenschaften Die Spezifikation von Umgebungseigenschaften ist mit zwei Problemen konfrontiert: • Information Hiding: nicht alle veränderbaren Variablen können in der Spezifikation genannt werden. • Zustandserweiterung: Die Supertypspezifikation sollte unabhängig von den Implementierungen der Subtypen sein. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.2 Konformität von Subtypen 402 Beispiel(Umgebungseigenschaften): class C { public int a = 0; private int b = 0; public static int c = 123; ... /*@ public normal_behavior @ assignable a; @*/ public void m(){ a++; b++; } } class Foo { void mfoo() { ... x.m() ... } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 class D extends C { public int d = 0; ... public void m(){ super.m(); d = 87; C.c = 4 ; ... } } 4.2 Konformität von Subtypen 403 Ansatz: • Verwende abstrakte Attribute/Variablen, Abhängigkeitsund Repräsentationsklauseln. • Information Hiding: Abstrakte Attribute können von nicht zugreifbaren Attributen abhängen. • Zustandserweiterung: Die Abhängigkeiten abstrakter Attribute können in Subtypen erweitert werden. (Beispiel dazu im folgenden Unterabschnitt) A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.2 Konformität von Subtypen Zusammenwirken der Techniken Spezifikationstechniken für objektorientierte Programme verfolgen zwei Ziele: • Spezifikation von Eigenschaften durch Annotation von Programmen. • Vollständige Spezifikation von Typen und damit auch Grundlage für das Verständnis der Verhaltenskonformität bei Subtypen. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 404 4.2 Konformität von Subtypen 405 Vorgehen: • Zusammenwirken der Techniken mit Schwerpunkt abstrakte Spezifikation an einem Beispiel • Verhaltenskonformität bei Subtypen Beispiel(Zusammenwirken der Techniken): Das behandelte Beispiel ist eine Java-Adaption des zentralen Beispiels aus: K. R. M. Leino, G. Nelson: Data abstraction and information hiding, Transactions on Programming Languages and Systems 24(5): 491-553 (2002). A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.2 Konformität von Subtypen 406 Reader BuffReader BlankReader BlankReaderImpl A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.2 Konformität von Subtypen Das Beispiel demonstriert vor allem die Behandlung von Zustandserweiterungen in Subtypen. public interface Reader { //@ public model instance boolean valid; //@ public model instance Object state; /*@ public normal_behavior @ requires valid; @ assignable state; @ ensures -1 <= \result @ && \result < 65535 ; @*/ public int getChar(); /*@ public normal_behavior @ requires valid; @ assignable valid, state; @*/ public void close(); } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 407 4.2 Konformität von Subtypen public abstract class BuffReader implements Reader { protected /*@ spec_public @*/ int lo, cur, hi; protected /*@ spec_public @*/ char[] buff; //@ public model boolean svalid; /*@ public represents valid <@ this != null && @ 0 <= lo && lo <= cur && cur <= hi && @ buff != null && hi-lo <= buff.length && @ svalid ; @*/ public int getChar() { if( cur == hi ) refill(); if( cur == hi ) return -1; cur++; return buff[cur-lo-1]; } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 408 4.2 Konformität von Subtypen /*@ public normal_behavior @ requires valid; @ assignable state; @ ensures cur == \old(cur) ; @*/ public abstract void refill(); //@ depends valid <- lo,cur,hi, buff, svalid; //@ depends state <- lo,cur,hi, buff, buff[*]; //@ depends svalid <-lo, hi, buff; } public interface BlankReader extends Reader { /*@ public normal_behavior @ requires 0 <= n; @ assignable valid, state; @ ensures valid && \result == this ; @*/ public BlankReader init( int n ); } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 409 4.2 Konformität von Subtypen Eine triviale Anwendung von BlankReader und BlankReaderImpl (s. nächste Seite): public class ReaderTest { public static void main( String[] args ) { BlankReader br = new BlankReaderImpl(); br.init(1000000); int count = 0; int chr; do { chr = br.getChar(); count++; } while( chr != -1 ); br.close(); System.out.println(count); } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 410 4.2 Konformität von Subtypen public class BlankReaderImpl extends BuffReader implements BlankReader { private int num; //@ private represents svalid <- hi <= num ; public BlankReader init( int n ) { num = n; buff = new byte[ Math.min(n,8192) ]; lo = 0; cur = 0; hi = buff.length; for( int i = 0; i < hi; i++ ) { buff[i] = 32; } return this; } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 411 4.2 Konformität von Subtypen public void refill() { lo = cur; hi = Math.min( lo+buff.length, num ); } public void close() {} //@ private depends state <- num ; //@ private depends svalid <- num ; // Repraesentation von state nicht betrachtet } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 412 4.2 Konformität von Subtypen Verhaltenskonformität bei Subtypen Bisher: • Spezifikationstechniken • Spezifikationsprobleme Weitergehendes Thema: • Was bedeutet es genau, dass sich ein Typ semantisch konform zu einem anderen verhält? A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 413 4.2 Konformität von Subtypen 414 Definition(konforme Subtypen): Ein Typ S ist ein (verhaltens-) konformer Subtyp von einem Typ T, wenn S-Objekte an allen wesentlichen Programmkontexten, an denen T-Objekten zulässig sind, die relevanten Eigenschaften von T-Objekten haben. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.2 Konformität von Subtypen Problematik: • Was sind die wesentlichen Programmkontexte: x instanceof S • Was sind die relevanten Eigenschaften, wie sind sie formuliert: Information Hiding, Abstraktion, Effizienz A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 415 4.2 Konformität von Subtypen 416 Lösungsansatz: • Betrachte nur die Methodenschnittstelle, d.h. keine Konstruktoren, keine Information über den Implementierungstyp. • Gehe vom spezifizierten Verhalten aus, wobei bestimmte Ausführungsmodelle und Spezifikationstechniken zugrunde gelegt werden. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.2 Konformität von Subtypen Literatur: B. H. Liskov, J. M. Wing: A Behavioral Notion of Subtyping. Transactions on Programming Languages and Systems 16(6): 1811-1841 (1994). Bemerkung: • Das allgemeine Problem zur präzisen Spezifikation konformer Subtypbildung bzgl. Ausführungsmodellen wie in Java ist nicht gelöst. • Konforme Subtypbildung ist auch und gerade für die Komponentenprogrammierung von zentraler Bedeutung. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 417 4.3 Module und Modularität 4.3 Module und Modularität A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 418 4.3 Module und Modularität 419 Bisher haben wir Spezifikation auf Objekt- bzw. Klassenebene betrachtet. Hier werden zwei Aspekte angesprochen die über diese Ebene hinausgehen: 4.3.1 Eigenschaften auf Modulebene 4.3.2 modulare Spezifikation A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 (vgl. Folie 178 ) 4.3 Module und Modularität 4.3.1 Eigenschaften auf Modulebene A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 420 4.3 Module und Modularität 421 Ein objektorientiertes Modul besteht aus mehreren Klassen. Häufig gibt es Eigenschaften, die über Klassengrenzen hinausgehen. Modulinvarianten beschreiben Eigenschaften von Assoziationen, an denen Objekte mehrerer Klassen eines Moduls beteiligt sind. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.3 Module und Modularität 422 Beispiel: Die Typen Component, Container, Window, etc. des Pakets java.awt erfüllen viele Invarianten, z.B.: • Ein Window-Objekt gehört zu keinem Container. • Ein Component-Objekt c gehört genau zu dem Container c.parent und zu keinem anderen Container. Bemerkung: Sprachkonstrukte und Techniken zur Spezifikation von Moduleigenschaften sind nur ansatzweise untersucht. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.3 Module und Modularität 423 4.3.2 Modulare Spezifikation A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.3 Module und Modularität 424 Die Entwicklung modularer Spezifikations- und Verifikationstechniken ist von fundamentaler Bedeutung, da solche Techniken die Voraussetzung für Skalierbarkeit sind. Die Entwicklung modularer Techniken für die objektorientierte Programmierung steht noch am Anfang. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.3 Module und Modularität 425 Modularität von Umgebungseigenschaften: Spezifikation durch Angabe der Variablen, die sich ändern dürfen. Problematik: • Sei m eine Methode in Modul P, die die Variable x ändern darf. • Sei y eine Variable in einem Modul Q, das P benutzt, so dass y in P nicht sichtbar ist, aber von x abhängig ist. Dann führt eine Änderung von x auch zu einer Änderung von y. Diese ist aber in P nicht erkennbar. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.3 Module und Modularität 426 Beispiel(Modularität von Umgebungseigenschaften:): Wir benutzen eine Listenklasse, um eine Klasse für Mengen zu implementieren, d.h. wir stützen eine Abstraktionsebene auf eine andere ab: public abstract class List { /*@ public model non_null @ JMLObjectSequence listValue; @*/ protected Node first, last; /*@ protected depends listValue <@ last, last.next, first, first.values; @ protected represents listValue <@ (first == null ? @ new JMLObjectSequence() @ : first.values); @*/ A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 4.3 Module und Modularität /*@ public normal_behavior @ requires o != null; @ modifies listValue; @ ensures listValue.equals(old(listValue.insertBack(o))); @*/ public void append( Object o) { if (last==null) { first = new Node(null, null, o); last = first; } else { last.next = new Node(null, last, o); last = last.next; } } /* ... */ } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 427 4.3 Module und Modularität public class Node { /*@ public model non_null @ JMLObjectSequence values; @*/ public Node next, prev; public Object val; /*@ public depends values <@ next, next.values, prev, val; @*/ /*@ public represents values <@ (next == null ? @ new JMLObjectSequence(val) @ : next.values.insertFront(val)); @*/ public Node(Node nx,Node pv,Object valp) { next = nx; prev = pv; val = valp; } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 428 4.3 Module und Modularität public abstract class Set { /*@ public model non_null @ JMLObjectSet setValue; @*/ protected /*@ non_null @*/ List theList; /*@ protected depends setValue <@ theList, theList.listValue; @*/ /*@ protected represents setValue \such_that @ (\forall Object o; o != null; @ theList.listValue.has(o) @ <==> setValue.has(o)); @*/ /*@ public normal_behavior @ requires o != null; @ modifies setValue; @ ensures setValue.has(o); @*/ public void insert( Object o) { if (!theList.contains(o)) { theList.append(o); } } } A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 429 4.3 Module und Modularität 430 Abschließende Bemerkungen zu Kapitel 4: • Verschiedene Techniken zur Spezifikation von Programmund Schnittstelleneigenschaften • Problematik im Zusammenhang mit Subtypen • Testfälle können auch zur Spezifikation eingesetzt werden (JML erlaubt die Einbettung von Testfällen in Spezifikationen). • Am Ende des Entwicklungsprozesses bilden Spezifikation und Programm zwei Systembeschreibungen. • Verfeinerung von Spezifikation wurden nicht behandelt. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 5. Verifikation spezifizierter Eigenschaften 432 5. Verifikation spezifizierter Eigenschaften 5.1 Techniken zur Verifikation 5.2 Verifikation durch Beweis A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 433 Definition(Verifikation): Verifikation bedeutet den Nachweis, dass Software bestimmte, explizit beschriebene Eigenschaften besitzt. Beschreibung kann erfolgen durch: • Testfälle • Spezifikation der Funktionalität (Invarianten, Vor- und Nachbedingungen, ... ) • Spezifikationen nicht-funktionaler Eigenschaften, z.B. Antwortzeiten, Codegröße, ... . Verifikation prüft also die Übereinstimmung von zwei expliziten Beschreibungen. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 5.1 Techniken zur Verifikation 5.1 Techniken zur Verifikation A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 434 5.1 Techniken zur Verifikation Grundlegende Techniken zur Verifikation: • • • • Testen mit Testfällen Testen durch dynamisches Prüfen erweitertes statisches Prüfen Verifikation durch Beweis A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 435 5.1 Techniken zur Verifikation 436 Testen mit Testfällen • Beschreibe das ,,Soll-Verhalten“ der Software durch eine (endliche) Menge von Eingabe-Ausgabe-Paaren bzw. von Paaren aus Eingaben und Zustandssequenzen. • Prüfe, ob die Software zu den Eingaben der Testfälle die entsprechenden Ausgaben liefert / Zustände durchläuft. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 5.1 Techniken zur Verifikation 437 Vorteile: • Testfälle sind einfach zu verstehen; weitergehende Spezifikation ist nicht erforderlich. • Durchführung von Tests ist einfach. Nachteile: • Spezifikation ist immer unvollständig. • Problematik in der OO-Programmierung: Bestimmung der relevanten Objektgeflechte. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 5.1 Techniken zur Verifikation Testen durch dynamisches Prüfen • Beschreibe das ,,Soll-Verhalten“ der Software durch ausführbare Programmspezifikation. • Prüfe zur Laufzeit, ob die Spezifikation erfüllt ist. Dazu können beliebige Eingaben verwendet werden. Anders als beim Testen mit Testfällen wird also das Verhalten des Programms an bestimmten Stellen automatisch während der Auswertung geprüft. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 438 5.1 Techniken zur Verifikation 439 Vorteile: • Fehlermeldung kann automatisiert und komfortabel gestaltet werden. • Gegenüber Testen mit Testfällen: Angabe des Ausgabeverhaltens ist nicht notwendig. • Gegenüber statischen Verfahren: Einfacher zu handhaben. Nachteile: • Gegenüber Testfällen: Spezifikation muss vorhanden sein und ist fehleranfälliger als Testfälle. • Gegenüber Verifikation durch Beweis: Unvollständigkeit und schwächere Spezifikationen. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 5.1 Techniken zur Verifikation 440 Erweitertes statisches Prüfen • Beschreibe das ,,Soll-Verhalten“ der Software durch Programmspezifikation. • Nutze alle automatische Prüftechniken zur statischen Analyse der Konsistenz von Programm und Spezifikation. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 5.1 Techniken zur Verifikation Vorteile: • Gegenüber Testen: Vollständigere Abdeckung des Verhaltens; mächtigere Spezifikationen möglich. • Gegenüber Verifikation durch Beweis: Einfacher zu handhaben, weil vollautomatisch. Nachteile: • Gegenüber Testen: Spezifikation muss höheren Ansprüchen genügen, insbesondere reichen isolierte Eigenschaften im Allg. nicht aus. • Gegenüber Verifikation durch Beweis: Unvollständigkeit und schwächere Spezifikationen. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 441 5.1 Techniken zur Verifikation Erweitertes statisches Prüfen basiert auf recht unterschiedlichen Techniken: • statischer Daten- und Kontrollflussanalyse • Modellprüfung endl. Zustandsräume (model checking) • Automatisierung klassischer Programmbeweistechniken A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 442 5.1 Techniken zur Verifikation Verifikation durch Beweis • siehe nächsten Abschnitt Bemerkung: Die unterschiedlichen Verifikationstechniken ergänzen sich und können kombiniert werden. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 443 5.2 Verifikation durch Beweis 5.2 Verifikation durch Beweis A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 444 5.2 Verifikation durch Beweis 445 Verifikation im engeren Sinne bedeutet den Beweis spezifizierter Eigenschaften mit logischen Mitteln. Im Gegensatz zum Testen erlaubt Verifikation durch Beweis, die Korrektheit zu zeigen, d.h. insbesondere die Abwesenheit von Fehlern. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 5.2 Verifikation durch Beweis 446 Prinzipieller Ansatz • Grundlage für die Beweise ist ein mathematisches oder formal-logisches Spezifikations- und Verifikationsframework. • Übersetze Spezifikationen und Programme in die Formeln des Framework. • Beweise die resultierenden Beweisverpflichtungen. Wir erläutern den Ansatz anhand eines Frameworks auf der Basis einer Hoare-Logikerweiterung. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 5.2 Verifikation durch Beweis 447 Spezifikations- und Verifikationsframework Als logische Grundlage betrachten wir eine sortierte Prädikatenlogik mit algebraischen Datentypen (vgl. Abschnitt 1.2 ). Die statische Semantik der betrachteten Programmiersprache wird damit spezifiziert (vgl. Abschn. 2.2.2 ). Auf dieser Basis lassen sich Eigenschaften über Programmzustände formulieren. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 5.2 Verifikation durch Beweis Programmspezifikationen: • Invariante zum Typ T ( inv[T] ) • Vor- und Nachbedingungsspezifikation zur Methode m in Typ T ( req[T:m], ens[T:m] ) • Veränderungsklauseln ( mod[T:m] ) A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 448 5.2 Verifikation durch Beweis 449 Programmlogik: • Hoare-Logik für partielle Korrektheit mit zusätzlichen Regeln für OO-Konstrukte • Formeln sind Tripel { P } c { Q }, wobei ◦ P, Q Formeln der Prädikatenlogik sind; ◦ c eine Anweisung, virtuelle Methode oder Methodenimplementierung ist. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 5.2 Verifikation durch Beweis 450 Definition(Methodenimplementierung): Eine Methodenimplementierung K@m ist eine Deklaration einer Methode mit Rumpf in Klasse K. Definition(virtuelle Methode): Zu jeder dynamisch zu bindenden Methode m, die ein Typ T selbst deklariert oder erbt, führen wir eine virtuelle Methode T:m ein, die das Verhalten aller Methoden m in Subtypen von T zusammenfasst. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 5.2 Verifikation durch Beweis Umsetzung von Programmspezifikationen Die Spezifikation eines vollständigen Programms Π wird umgesetzt in eine Menge von Tripeln. Seien: • S0 ein Typ, • m eine Methode in S0 und • S1, ..., Sk die Supertypen von S0, in denen m existiert, mit Si < Si+1 • T = { T | T ist Typ in Π } V • INV ⇔ T ∈T inv[T] A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 451 5.2 Verifikation durch Beweis 452 Für S0:m ist zu zeigen: V { INV ∧ 0≤i≤k req[Si:m] } S0:m { INV } V V { INV ∧ 0≤i≤k req[Si:m] } S0:m { 0≤i≤k ens[Si:m] } sowie down(mod[Si:m]) ⊆ down(mod[Si+1:m]) V { INV ∧ 0≤i≤k req[Si:m] ∧ v ∈ / down(mod[Sk :m]) ∧ v = Z } S0:m { v = Z } wobei down( vl: set of VAR ) die Menge aller Variablen bezeichnet, die abhängige Variablen in vl besitzen. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 5.2 Verifikation durch Beweis Bemerkung: Details der Umsetzung hängen sowohl von der Spezifikationssprache als auch von der verwendeten Programmlogik ab. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 453 5.2 Verifikation durch Beweis 454 Hoare-Logik für objektorientierte Programme Zusätzlich zu den Eigenschaften für klassische prozedurale Programme muss eine Logik für moderne objektorientierte Programme mit Folgendem umgehen können: • dynamisch allozierte Objekte • Subtyping • dynamische Bindung • Ausnahmebehandlung Wir illustrieren diese Aspekte an ausgewählten Regeln für eine Teilsprache von Java. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 5.2 Verifikation durch Beweis 455 Axiom für Leseanweisung: {(y 6= null ∧ P [$(y.a)/x]) ∨ (y = null ∧ P [$hN ullP Exci/$, new($, N ullP Exc)/exc])} x = y.a; {P } Regel für Sequenzanweisung: {P } s1 {(exc 6= null ∧ Q) ∨ (exc = null ∧ R)} {R} s2 {Q} {P } s1 s2 {Q} A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 5.2 Verifikation durch Beweis 456 Regeln für Methodenaufruf: {P } vm(styp(y, x = y.m(e); ), m) {Q[res/x ]} {(y 6= null ∧ P [y/this, e/par ])∨ (y = null ∧ Q[$hNullPExci/$, new ($, NullPExc)/exc])} x = y.m(e); {Q} {P } x = y.m(e); {Q} {P [w/Z]} x = y.m(e); {Q[w/Z]} wobei w eine Programmvariable ungleich x ist und Z eine logische Variable. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 5.2 Verifikation durch Beweis 457 Bemerkung: • Obige Regeln sollen wichtige Ideen demonstrieren. Auf Details und die Behandlung rekursiver Methoden muss hier verzichtet werden. • Die Korrektheit der Logik kann bzgl. der operationalen Semantik gezeigt werden. A. Poetzsch-Heffter: FASOOP Wintersemester 02/03 6. Ausblicke 459 • Die Entwicklung der vorgestellten Techniken ist noch im Gange: ◦ Integration ◦ Modulebene ◦ Modularität • Wichtige zukünftige Aspekte sind außerdem: ◦ Zusammenwirken mit Entwicklungsmethode ◦ Integration mit Entwurfstechniken ◦ Werkzeug- und weitere Sprachunterstützung MMISS: Kurztitel September 15, 2003