Die Präsentation wird geladen. Bitte warten

Die Präsentation wird geladen. Bitte warten

Prof. Dr. Max Mühlhäuser Dr. Guido Rößling

Ähnliche Präsentationen


Präsentation zum Thema: "Prof. Dr. Max Mühlhäuser Dr. Guido Rößling"—  Präsentation transkript:

1 Prof. Dr. Max Mühlhäuser Dr. Guido Rößling
Grundlagen der Informatik I Thema 17: Statische Typisierung Subtyp-Polymorphie Prof. Dr. Max Mühlhäuser Dr. Guido Rößling

2 Worum geht es in dieser Vorlesung?
Das “magische Dreieck” der Programmiersprachen Welche Invarianten gibt es in meinem Programm? sicher mächtig einfach Wie einfach ist ein Programm zu verstehen? Welche Ideen können in der Sprache direkt ausgedrückt werden? Wir hatten diese Abwägung schon mal: Die Diskussion pro/contra Zuweisungen

3 Werte und Typen Definition: Ein Typ ist eine Menge von Werten, die irgendeine interessante gemeinsame Eigenschaft haben Gemeinsame Eigenschaften  gemeinsame Operationen! Ist ein Wert v Element eines Typs T, sagen wir v hat den Typ T Beispiel: {0,1,2,…} ist ein Typ: die natürlichen Zahlen Beispiel: {1,2,3,4,5,6} ist ein Typ: die Menge der deutschen Schulnoten Ein Wert kann zu mehreren Typen passen Andere Typen: Symbol, String, …

4 Werte und Typen Viele Abstraktionsmechanismen in Programmiersprachen beruhen auf der Abstraktion Wert  Typ Wir abstrahieren (* 4 4), (* 3 3) zu (square 4), (square 3), wobei square = (lambda (x) (* x x)) Wir gehen implizit davon aus, dass das x in der Funktionsdefinition den Typ Number hat, denn nur für diesen ist * definiert Funktionen haben ebenfalls einen Typ, den wir mit Hilfe von Verträgen ausgedrückt haben z.B. (lambda (x) (* x x)) hat den Typ number  number

5 Typfehler Ein Typfehler tritt auf, wenn eine berechnende Einheit (z.B. ein Wert oder eine Funktion) in einer zu dem Konzept, das sie darstellt, inkonsistenten Art und Weise benutzt wird Hardwarefehler unerlaubter Instruktionsfehler unerlaubter Speicherverweis Versehentliche Semantik z.B. kann in int_add(3, 4.5) der Wert 4.5 falsch interpretiert werden als ein Ganzzahlwert, der nicht mit 4.5 in Beziehung steht

6 Typsysteme Ein Typsystem ist ein Mechanismus, der jedem Wert einen Typ (oder Menge von Typen) zuordnet und verhindert, dass Typfehler auftreten. Beispiel: Scheme verhindert den Typfehler durch Abbruch des Programms Diese Fehlermeldung bekommen wir aber erst, wenn wir das Programm ausführen! > (define x 'hello) > (+ x 5) +: expects type <number> as 1st argument, given: 'hello; other arguments were: 5

7 Statische Typsysteme In einem statischen Typsystem kann man ein Programm vor der Ausführung auf mögliche Typfehler überprüfen Ein großer Vorteil, weil man den Fehler bemerkt, bevor es zu spät ist Zuverlässiger als “Testen”, weil Tests nur die Anwesenheit, aber nicht die Abwesenheit von Fehlern überprüfen können In kompilierten Sprachen geschieht diese Typüberprüfung häufig während der Kompilierung Auch interpretierte Sprachen können aber statische Typsysteme haben Beispiel für statisch typisierte Sprache: Java

8 Statische Typsysteme Werden Typfehler erst zur Laufzeit erkannt, spricht man von dynamisch typisierten Sprachen Werden Typfehler gar nicht erkannt, spricht man von untypisierten Sprachen Beispiel: Assembler In der Realität werden viele Programmiersprachen statisch geprüft, garantieren aber keine Sicherheit (manchmal schwaches Prüfen genannt) Manche Sprachen haben ein statisches Typsystem, welches aber nicht alle Typfehler erkennt “gut genug”, 80/20 Regel

9 Ebenen von Typsicherheit

10 Statisch typisierte Sprachen
Beispielsprachen: Java, C++, C#, Haskell, ML Der Typ jeder Variable kann zur Kompilierzeit bestimmt werden Die meisten statisch typisierten Sprachen erreichen dieses Ziel, indem sie explizite Deklarationen der Typen verlangen. Es ist in Java unmöglich, eine Variable zu deklarieren, ohne ihren Typ anzugeben. class Counter { int n; Counter(int value) { n = value; } //...

11 Statisch typisierte Sprachen
Statisch typisierte Sprachen mit impliziten Deklarationen verwenden vorgegebene Konventionen FORTRAN: Variablen, die mit I, J, K, L, M, N anfangen, enthalten Ganzzahlen Es gibt statisch typisierte Sprachen mit Typdeduktionsfähigkeiten (type inference) Gegeben folgende Deklaration, kommt der ML-Compiler zu der Folgerung, dass x eine Zahl ist. fun square(x) = x * x

12 Dynamisch typisierte Sprachen
In dynamisch typisierten Sprachen wird der Typ einer Variable dynamisch gebunden Es sind keine Typdeklarationen notwendig. Sehr bequem! Ermöglicht die Definition von flexiblen Funktionen, für die der Typ der Argumente egal ist. Beispiele: Scheme, Smalltalk, Self, Python, etc.

13 Typprüfung Typprüfung stellt sicher, dass die Operanden eines Ausdrucks kompatibel mit den Operatoren sind Sowie dass es sich bei einer Nachricht an einem Objekt um eine Methode in der Schnittstelle des Objektes handelt Kompatible Typen sind entweder die gleichen Typen, oder können implizit ineinander konvertiert werden. Automatische Typkonvertierung wird coercion genannt Siehe die Diskussion über automatische Typkonvertierungen in Abschnitt SICP (V 9) integer  rational  real  complex Ein Typfehler entsteht bei der Anwendung einer Operation an einem Operanden mit einem unerlaubten Typ.

14 Typprüfung Bei statischen Typbindungen kann die Typprüfung statisch (zur Kompilierzeit) stattfinden (statische Typprüfung). Bei dynamischen Typbindungen kann die Typprüfung nur zur Laufzeit geschehen (dynamische Typprüfung). In einigen statisch typisierten Sprachen können einige Typprüfungen auch nur zur Laufzeit durchgeführt werden Mehr dazu später

15 Statische vs. dynamische Typprüfung
String x = "hello"; x = x * 10; Zur Kompilierzeit: ^^^^^^ * not defined for type String (define x 'hello) (+ x 1) Zur Laufzeit: +: expects type <number> as 1st argument, given: hello; other arguments were: 1 Ungültige Verwendung von Variablen werden von dem Compiler nicht entdeckt und führen zu einem Laufzeitfehler

16 Statische vs. dynamische Typprüfung
String s = new String("Hello World"); s.determineLength(); Compile program… Error in ... (line 3) s.determineLength(); ^^^^^^^^^^^^^^^^ Method determineLength() not defined for type String

17 Statische vs. dynamische Typprüfung
(define (make-counter n) (define (increment) (set! n (+ n 1)) n) (define (dispatch msg) (cond ((eq? msg 'increment) increment) (else (error "undef operation:" msg))) ) dispatch (define ca (make-counter 1)) (ca 'increment) (ca 'decrement) Zur Laufzeit: undef operation: decrement

18 Statisches Typisieren ist konservativ
Ein perfekter statischer Typprüfer wäre ein Typprüfer, der einen Typfehler dann und genau dann meldet, wenn der Typfehler während der Programmausführung auftreten wird Ein perfekter Typprüfer existiert nicht! Folgt aus bestimmten Unentscheidbarkeitsergebnissen aus der Theoretischen Informatik („Halteproblem“) Somit nähern statische Typprüfer das Laufzeitverhalten an: Sie sind konservativ Sie “bleiben auf der sicheren Seite” Wann immer ein Typfehler zur Laufzeit auftreten würde, wird er vom Typprüfer entdeckt Es gibt aber auch Programme, die vom Typprüfer abgelehnt werden, die ohne Fehler ausgeführt werden könnten

19 Sollten Sprachen statisch geprüft werden?
Argumente für statisches Prüfen Wirtschaftlichkeit der Ausführung z.B. arithmetisch, Methodenauflösung Wirtschaftlichkeit des Kompilierens separate Kompilierung möglich Wirtschaftlichkeit der Entwicklung in kleinem Umfang kann einen großen Teil von Routineprogrammierfehlern abfangen, besser als unvollständiges manuelles Testen bietet Dokumentation zum Quelltext an Gebrauch des Typprüfers als Entwicklungswerkzeug, z.B. eine Klasse umbenennen, um alle Referenzen darauf zu entdecken Wirtschaftlichkeit der Entwicklung im großen Umfang Teams können Schnittstellen vereinbaren, die vom Compiler erzwungen werden Abstrahieren weg von der Implementierung großer Komponenten

20 Sollten Sprachen statisch geprüft werden?
Argumente gegen statisches Prüfen Quelltext langatmiger viele offensichtliche Deklarationen Inflexibilität Statisches Prüfen ist konservativ probieren Sie das z.B. in Java Identitätsfunktion implementieren ohne Typumwandlung Das Argument von equals(Object other) sollte den selben Typ haben wie der Empfänger clone() gibt immer eine Instanz desselben Typs wie der Empfänger zurück Viele Mechanismen wurden entwickelt, um mit diesen Problemen umzugehen Typinferenz, Typsysteme höherer Ordnung, ... Aber diese Mechanismen können sehr komplex sein Ein statischer Typprüfer ist ein mächtiger Helfer, solange die Programme in den Grenzen dessen liegen, was das Typsystem ausdrücken kann. Wenn wir über diese Grenzen hinausgehen wollen, steht uns das Typsystem im Weg.

21 Statischer und dynamischer Typ
Der Typ eines Objekts ist immer die Klasse, deren Instanz es ist  dieser Typ ist immer dynamisch an ein Objekt gebunden. Bei Variablen unterscheiden wir den statischen und dynamischen Typ: Der statische Typ einer Variablen ist der Typ, an den sie bei der Deklaration gebunden wird. Er verändert sich während der Ausführung nicht. Der dynamische Typ zu einem Zeitpunkt t während der Ausführung ist der Typ des Objekts, auf das die Variable zum Zeitpunkt t verweist.

22 Statischer und dynamischer Typ
Statischer Typ der Variablen shape Der dynamische Typ von shape ist ab diesem Zeitpunkt Rectangle Der dynamische Typ der Variablen shape ist zu diesem Zeitpunkt Circle Shape shape = null; shape = new Circle(10, 20, 16); shape.draw(canvas); shape = new Rectangle(10, 10, 20, 5); Hier wird in einem Programm ein und derselbe Name an verschiedenen Stellen benutzt, um zwei verschiedene Objekte zu bezeichnen. Eine solche Variable nennt man polymorph (griechisch "vielgestaltig"). In der realen Welt gibt es mehrere Objekte namens shape, und welches gemeint ist, hängt vom Kontext ab. In der Welt der grafischen Objekte ist es genauso.

23 Statischer und dynamischer Typ
Kann man ein grafisches Objekt beliebigen Typs an eine beliebige Variable zuzuweisen? Natürlich nicht! Darum geht es ja bei Typsystemen: Ein Typ regelt, wie ein Name benutzt werden kann Der Typ schränkt die Menge der Werte ein, die einer Variablen zugewiesen werden können … und die darauf ausführbaren gültigen Operationen Nur Instanzen einer direkten oder indirekten Subklasse ihres statischen Typs können an eine Variable zugewiesen werden. public void main(String[] args) { Circle shape = null; shape = new Rectangle(10, 10, 20, 5); // ... } H

24 Das Substitutionsprinzip
Ein Objekt (grafisches Objekt) einer Subklasse kann überall dort verwendet werden, wo ein Objekt (grafisches Objekt) einer Superklasse erwartet wird. Diese Substituierbarkeitsrelation ist transitiv. Die Deklaration B var; impliziert: A B C An var kann jedes Objekt der Klassen B, C und all ihrer Subklassen zugewiesen werden (Transitivität der Substituierbarkeit). Jede in A und B deklarierte Operation kann auf var (auf jedem in var gespeicherten Objekt) aufgerufen werden. Es ist nicht erlaubt, auf var eine Operation aufzurufen, die in C deklariert ist, aber nicht in A oder B.

25 Das Substitutionsprinzip
Ein Objekt einer Subklasse kann einem Objekt einer Superklasse als Repräsentant dienen: Seine Struktur enthält alle Attribute eines Objekts der Superklasse Es kann alle Operationen ausführen, die für Objekte der Superklasse definiert wurden Das Gegenteil trifft jedoch nicht zu! Ein Objekt einer Superklasse ist nicht spezialisiert genug, um die Rolle des Objekts einer Subklasse zu spielen. Die Deklaration B var; heißt nicht “var hat genau den Typ B“; sondern “das Verhalten von var ist konform zu B“.

26 Substitutionsprinzip: eine Analogie
Ein Kunde einer Autovermietung bestellt ein vierrädriges Fahrzeug zum Transport einiger Möbelstücke von A nach B Es ist in Ordnung, wenn er einen Kleinbus erhält. Ein Kleinbus hat vier Räder. Hat er jedoch einen Lastwagen bestellt, dann ist es nicht OK, ihm ein beliebiges vierrädriges Fahrzeug zu geben Der Kleinbus könnte zu wenig Ladefläche haben. Fahrzeug Zweirädrig Vierrädrig Motorrad Fahrrad Lastwagen Kleinbus

27 Gleicher Programmtext, aber verschiedene Bedeutung
Subtyp-Polymorphie Shape shape = null; shape = new Circle(10, 20, 16); shape.draw(canvas); shape = new Rectangle(10, 10, 60, 20); Gleicher Programmtext, aber verschiedene Bedeutung POLYMORPHIE

28 Subtyp-Polymorphie Jedes grafische Objekt befolgt Befehle gemäß seiner Programmierung: paint(g); Circle vs Rectangle Dies ist besonders interessant, wenn mehrere grafische Objekte dieselben Befehle erhalten, sie aber unterschiedlich implementieren.

29 Noch einmal Subtyp-Polymorphie
Nun, da wir Schnittstellen kennen, betrachten wir statische und dynamische Typen von Variablen, Subtyp- Polymorphie und Substituierbarkeit gemeinsam. Zu diesem Zweck werden wir das UML-Klassendiagram auf dieser Folie benutzen. Es zeigt eine einfache Typ- und Klassenhierarchie.

30 Noch einmal Subtyp-Polymorphie
Das Modell stellt fünf Typen dar: vier Klassen und eine Schnittstelle. Obwohl das Modell "Klassendiagramm" genannt wird, ist es eigentlich ein Typdiagramm. Jede Java-Klasse und Schnittstelle deklariert einen benutzerdefinierten Datentyp.

31 Noch einmal Subtyp-Polymorphie
Von einem implementierungs-unabhängigen (also typorientierten) Standpunkt aus betrachtet, repräsentiert jedes der fünf Rechtecke einen Typ. Vom Standpunkt der Implementierung aus betrachtet, sind vier dieser Typen durch Klassen definiert, und einer durch eine Schnittstelle.

32 Implementierungs-Hierarchie
interface Itype {   String m2(String s);   String m3(); } class Base {   public String m1() {     return "Base.m1()";   }   public String m2(String s) {     return "Base.m2(" + s + ")";   } } class Derived extends Base implements Itype {   public String m1() {     return "Derived.m1()";   }   public String m3() {     return "Derived.m3()";   } }

33 Implementierungs-Hierarchie
class Derived2 extends Derived {   public String m2(String s) {     return "Derived2.m2(" + s + ")";   }   public String m4() {     return "Derived2.m4()";   } } class Separate implements IType {   public String m1() {     return "Separate.m1()";   }   public String m2(String s) {   return "Separate.m2(" + s + „)";   }   public String m3() {   return "Separate.m3()";   } }

34 Referenzen als Bullaugen
Derived2 derived2 = new Derived2(); Dieser Ausdruck tut zweierlei: er deklariert die explizit getypte Referenzvariable derived2, bindet derived2 an ein neu erzeugtes Derived2-Objekt. Es gibt ein Bullauge pro Operation im Typ Derived2. Das Derived2-Objekt bildet jede Derived2-Operation auf geeigneten Code ab, wie in der Implementierungs-Hierarchie vorgesehen. There is one hole for each Derived2 type operation. The actual Derived2 object maps each Derived2 operation to appropriate implementation code, as prescribed by the implementation hierarchy There is one hole for each Derived2 type operation. The actual Derived2 object maps each Derived2 operation to appropriate implementation code, as prescribed by the implementation hierarchy defined in the above code. For example, the Derived2 object maps m1() to implementation code defined in class Derived. Furthermore, that implementation code overrides the m1() method in class Base. A Derived2 reference variable cannot access the overridden m1() implementation in class Base. That does not mean that the actual implementation code in class Derived can't use the Base class implementation via super.m1(). But as far as the reference variable derived2 is concerned, that code is inaccessible. The mappings of the other Derived2 operations similarly show the implementation code executed for each type operation. Die Derived2-Referenz kann als Menge von Bullaugen gesehen werden, durch die das Derived2-Objekt gesehen wird.

35 Referenzen als Bullaugen
Derived2 derived2 = new Derived2(); Beispiel: Das Derived2-Objekt bildet m1() auf Code ab, der in der Klasse Derived definiert ist. Darüber hinaus überschreibt dieser Code die Implementierung von m1() in der Klasse Base. There is one hole for each Derived2 type operation. The actual Derived2 object maps each Derived2 operation to appropriate implementation code, as prescribed by the implementation hierarchy There is one hole for each Derived2 type operation. The actual Derived2 object maps each Derived2 operation to appropriate implementation code, as prescribed by the implementation hierarchy defined in the above code. For example, the Derived2 object maps m1() to implementation code defined in class Derived. Furthermore, that implementation code overrides the m1() method in class Base. A Derived2 reference variable cannot access the overridden m1() implementation in class Base. That does not mean that the actual implementation code in class Derived can't use the Base class implementation via super.m1(). But as far as the reference variable derived2 is concerned, that code is inaccessible. The mappings of the other Derived2 operations similarly show the implementation code executed for each type operation. Eine Derived2-Referenzvariable kann auf die überschriebene Methode m1() in Base nicht zugreifen. Der implementierende Code in der Klasse Derived kann aber die Implementierung in Base über super.m1() benutzen. Was die Referenzvariable derived2 angeht, ist dieser Code jedoch nicht sichtbar.

36 Mehrere Referenzen auf ein Objekt
Base base = derived2; Substituierbarkeit: Wir können das Derived2-Object, das an die Referenz derived2 gebunden ist, mit jeder Variablen eines Typs T referenzieren, wenn Derived2 zu T konform ist. Die Typhierarchie ergibt, dass Derived, Base, und IType Supertypen von Derived2 sind, Derived2 also zu ihnen konform ist. Daher kann z. B. eine Base-Referenz an das von derived2 referenzierte Objekt gebunden werden. ergibt eine weitere Menge von Bullaugen

37 Mehrere Referenzen auf ein Objekt
Es findet keine Veränderung an dem Derived2-Objekt oder seinen Operations-Abbildungen statt. Werden m1() oder m2(String) auf derived2 oder base aufgerufen, wird jeweils derselbe Code ausgeführt. String tmp; tmp = derived2.m1();    // Derived2 reference      // tmp is "Derived.m1()" tmp = derived2.m2("Hello"); // tmp is "Derived2.m2(Hello)" tmp = base.m1();      // Base reference             tmp = base.m2("Hello");         // tmp is "Derived2.m2(Hello)"

38 Mehrere Referenzen auf ein Objekt
Warum erhält man trotz verschiedener Referenzen identisches Verhalten? Ein Objekt weiß nicht, wer oder was seine Methoden aufruft. Ein Derived2-Objekt weiß nur, dass es, wenn es aufgerufen wird, den "Marschregeln" folgen muss, die von seiner Implementierungs-Hierarchie vorgegeben sind. Methoden-Auswahl (method dispatch) Diese Regeln legen fest, dass das Derived2-Objekt für die Methode m1() den in der Klasse Derived, und für m2(String) den in Derived2 definierten Code ausführen muss. Die vom referenzierten Objekt ausgeführte Aktion hängt nicht vom Typ der Referenzvariablen ab.

39 Mehrere Referenzen auf ein Objekt
Es findet keine Veränderung an dem Derived2-Objekt oder seinen Operations-Abbildungen statt. Jedoch kann auf die Methoden m3() und m4() nicht mehr durch die Referenz base zugegriffen werden. String tmp; // Derived2 reference tmp = derived2.m3(); // tmp is "Derived.m3()" tmp = derived2.m4(); // tmp is "Derived2.m4()" // Base reference tmp = base.m3();   // Compile-time error tmp = base.m4();   // Compile-time error

40 Mehrere Referenzen auf ein Objekt
Das Derived2-Objekt kann immer noch Aufrufe von m3() und m4() entgegen nehmen. Typrestriktionen, die solche Aufrufe über die Base-Referenz verhindern, treten zur Compilezeit auf. Die statische Typprüfung verhält sich wie ein Schild, indem sie Interaktionen zwischen Objekten nur durch explizit deklarierte Typ-Operationen erlaubt. Die statischen Typen der Referenzen definieren die Grenzen der Objektinteraktion. Das kann generalisiert werden: wird eine Supertyp-Referenz an ein Objekt gebunden, so wird dessen Benutzung eingeschränkt. Warum sollte ein Entwickler sich dafür entscheiden, Objekt-Funktionalität zu verlieren?

41 Mehrere Referenzen auf ein Objekt
Diese Entscheidung wird oft unfreiwillig gefällt. Angenommen, eine Referenzvariable ref ist an ein Objekt gebunden, dessen Klasse folgende Methodendefinition enthält: public String poly1(Base base) {   return base.m1(); } Folgender Aufruf ist erlaubt, weil der Parametertyp konform ist: ref.poly1(derived2); So although the method receives a Derived2 object, it may only access Base type operations. The developer of the implementation code does not necessarily choose to lose functionality. Durch den Methodenaufruf wird eine lokale Base-Referenz an das übergebene Objekt gebunden.

42 Mehrere Referenzen auf ein Objekt
Vom Standpunkt des Aufrufers, der das Derived2-Objekt übergibt, führt das Binden einer Base-Referenz durch den Implementierer von poly1(Base) zu einem Verlust von Funktionalität. Für Implementierer sieht jedes an poly1(Base) übergebene Objekt wie ein Base-Objekt aus. Den Implementierer kümmert es nicht, dass mehrere Referenztypen auf dasselbe Objekt zeigen können Für ihn wird ein und derselbe Referenztyp an alle Objekte gebunden, die an die Methode übergeben werden. Dass diese Objekte möglicherweise verschiedenen Typs sind, ist zweitrangig. Der Implementierer erwartet nur, dass das jeweilige Objekt alle Operationen des Typs Base auf entsprechende Implementierungen abbilden kann.

43 Eine Referenz auf mehrere Objekte
Interessantes polymorphes Verhalten tritt auf, wenn eine Referenzvariable nacheinander an mehrere Objekte ggf. unterschiedlichen Typs gebunden wird. Streng genommen, meint Objekttyp genau den durch die Klasse des Objekts definierten Typ. Derived2 derived2 = new Derived2(); Derived derived = new Derived(); Base base = new Base(); String tmp; tmp = ref.poly1(derived2); // tmp is "Derived.m1()" tmp = ref.poly1(derived);  // tmp is "Derived.m1()" tmp = ref.poly1(base);     // tmp is "Base.m1()"

44 Eine Referenz auf mehrere Objekte
Verschiedene Objekte werden durch dasselbe Bullauge betrachtet: Das Bullauge definiert die Abbildungen, die verfügbar sein sollten Verschiedene Objekte haben verschiedene Abbildungen.

45 Die Macht der Polymorphie
Der Code von poly1(Base) betrachtet jedes Objekt durch eine Base-Typ-Linse. Wird jedoch ein Derived2-Objekt übergeben, gibt die Methode ein Ergebnis zurück, das von Code in der Klasse Derived berechnet wurde! Werden die Klassen Base, Derived oder Derived2 später erweitert, wird poly1(Base) problemlos Objekte der neuen Klassen akzeptieren und den erwünschten Code ausführen. Polymorphie gestattet, dass die neuen Klassen lange nach der Implementierung von poly1(Base) hinzu kommen. That certainly seems like magic. However, a fundamental understanding reveals the inner workings of polymorphism. From a type-oriented perspective, the underlying object's actual implementation code is immaterial. The most important aspect of reference-to-object attachment is that the compile-time type-checker has guaranteed that the underlying object possesses a runtime implementation for each type operation. Polymorphism frees the developer from the implementation details of an object and allows design to occur using a type-oriented perspective. Therein lies a significant benefit in separating type and implementation (often referred to as separating interface and implementation).

46 Die Macht der Polymorphie
Wie geht das? Vom typorientierten Standpunkt aus ist der eigentliche Implementierungscode referenzierter Objekte unerheblich. Der wichtigste Aspekt der Bindung von Referenzen an Objekte ist der, dass durch die zur Compilezeit stattfindende Typprüfung garantiert werden kann, dass das referenzierte Objekt für jede Operation des Typs eine zur Laufzeit verfügbare Implementierung besitzt. Polymorphie befreit den Entwickler von der Pflicht, Implementierungsdetails einzelner Objekte zu kennen, und erlaubt statt dessen, einen Entwurf allein aus einer Typ-orientierten Perspektive anzufertigen. Darin liegt ein signifikanter Vorteil der Trennung von Typ und Implementierung (auch genannt "Trennung von Schnittstelle und Implementierung"). That certainly seems like magic. However, a fundamental understanding reveals the inner workings of polymorphism. From a type-oriented perspective, the underlying object's actual implementation code is immaterial. The most important aspect of reference-to-object attachment is that the compile-time type-checker has guaranteed that the underlying object possesses a runtime implementation for each type operation. Polymorphism frees the developer from the implementation details of an object and allows design to occur using a type-oriented perspective. Therein lies a significant benefit in separating type and implementation (often referred to as separating interface and implementation).

47 Java-Schnittstellen und Polymorphie
Java-Schnittstellen deklarieren benutzerdefinierte Typen Entsprechend erlauben Java-Schnittstellen polymorphes Verhalten durch den Aufbau einer Typvererbungsstruktur. Angenommen, eine Referenzvariable ref wird an ein Objekt gebunden, dessen Klasse folgende Methode enthält: public String poly2(IType iType) {   return iType.m3(); } Das Folgende illustriert polymorphes Verhalten in poly2(IType): Derived2 derived2 = new Derived2(); Separate separate = new Separate(); String tmp; tmp = ref.poly2(derived2); // tmp is "Derived.m3()" tmp = ref.poly2(separate); // tmp is "Separate.m3()"

48 Java-Schnittstellen und Polymorphie
Vom Typ-orientierten Standpunkt aus betrachtet gibt es keinen Unterschied zwischen den Beispielen zu poly2 und poly1. The above code resembles the previous discussion of polymorphic behavior inside poly1(Base). The implementation code in poly2(IType) calls method m3() for each object, using a local IType reference. As before, code comments note the String result of each call. Figure 5 shows the conceptual structure of the two calls to poly2(IType):

49 Java-Schnittstellen und Polymorphie
Es gibt jedoch einen wichtigen Unterschied in der Implementierung: In poly1(Base) werden durch die Base-Derived-Derived2-Klassenvererbungskette die benötigten Subtyp-Relationen etabliert, und das Überschreiben von Methoden führt zu den korrekten Abbildungen auf Implementierungscode. In poly2(IType) tritt ein völlig anderes Verhalten auf. Die Klassen Derived2 und Separate teilen sich keine gemeinsame Implementierungshierarchie Aber: Instanzen dieser Klassen stellen polymorphes Verhalten durch eine IType-Referenz an. Durch die Gruppierung von Objekten aus unterschiedlichen Implementierungshierarchien erlauben Java-Schnittstellen polymorphes Verhalten sogar in Abwesenheit gemeinsamer Implementierungen oder überschriebener Methoden.

50 Schnittstelle und Typ eines Objekts
Was meinen wir mit der Schnittstelle eines Objekts? Typischerweise bezeichnet das die Menge aller öffentlichen Methoden, die in der Klassenhierarchie des Objekts definiert werden: Die Menge aller öffentlich verfügbaren Methoden, die auf dem Objekt aufgerufen werden können. Auf dem Bild bezieht sich die Schnittstelle zum Objekt auf die mit "Derived2 Object” bezeichnete Ebene, die alle verfügbaren Methoden für das Derived2-Objekt anführt.

51 Schnittstelle und Typ eines Objekts
Diese Definition der Objektschnittstelle führt zu einer Implementierungs-zentrischen Sichtweise Man konzentriert sich auf die Laufzeitfähigkeiten eines Objekts, statt auf eine Typ-orientierte Sichtweise Um Polymorphie zu verstehen, müssen wir das Objekt jedoch vom typorientierten Standpunkt der mit "Base Reference" bezeichneten Ebene betrachten. Der Typ der Referenzvariablen diktiert dem Objekt eine Schnittstelle. Eine Schnittstelle, nicht die Schnittstelle. Gelenkt von Typkonformität, können mehrere typorientierte Sichtweisen an ein Objekt gebunden werden. Es gibt keine einzeln spezifizierte Schnittstelle zu einem Objekt.

52 Schnittstelle und Typ eines Objekts
Im Sinne von Typen ist die Schnittstelle zu einem Objekt die breitestmögliche Typ-orientierte Sicht darauf. Eine an das Objekt gebundene Supertypreferenz schränkt diese Sicht typischerweise ein. Das Typkonzept erfasst den Geist der Befreiung der Objektinteraktionen von Implementierungsdetails am besten. Anstatt sich auf die Schnittstelle eines Objekts zu beziehen, ermutigt eine typorientierte Perspektive dazu, sich auf den an das Objekt gebundenen Referenztyp zu beziehen. Der Referenztyp diktiert die erlaubte Interaktion mit dem Objekt. Denken Sie an den Typ, wenn Sie wissen wollen, was ein Objekt tun kann, im Gegensatz zu der Art und Weise, wie das Objekt es tut.

53 Objektschnittstelle (nochmals)
Auf Folie 30 haben sowohl das Derived2- als auch das Separate-Objekt eine Methode m1(). Wie soeben diskutiert, hat die Schnittstelle beider Objekte eine Methode m1(). Es gibt jedoch mit diesen beiden Objekten keine Möglichkeit, die Methode m1() polymorph zu nutzen. Es reicht nicht aus, dass jedes Objekt m1() hat. Ein gemeinsamer Typ mit der Operation m1() muss existieren, durch den man die Objekte sehen kann. Die Objekte sehen so aus, als würden sie sich m1() in ihren Schnittstellen teilen. Ohne einen gemeinsamen Supertyp ist Polymorphie in einer statisch getypten Sprache wie Java unmöglich. Der Grund ist, dass Subtyping in Java nicht strukturell sondern nominell definiert ist Strukturell : „passen“ die Methodensignaturen? Nominell: es muss zusätzlich eine explizite Subtypdeklaration (implements/extends Klausel) geben

54 Schlussbemerkungen Das Verständnis von Subtyp-Polymorphie erfordert einen Wechsel im Denken hin zu Typen statt Implementierungen. Typen definieren Objektgruppierungen und kontrollieren erlaubte Objektinteraktionen. Die hierarchische Struktur von Typvererbung ergibt die zum Erreichen polymorphen Verhaltens nötigen Typbeziehungen. Typen geben an, welche Methoden ein Objekt ausführen kann; Implementierung gibt an, wie ein Objekt auf Methodenaufrufe reagiert. Also bestimmen Typen Verantwortlichkeiten, und Klassen implementieren sie. Durch eine saubere Trennung von Typ und Implementierung ergibt sich, dass die beiden einen "Tanz der Objekte" kontrollieren: Typen bestimmen erlaubte Tanzpartner, und Implementierungen die Choreographie der Tanzschritte.


Herunterladen ppt "Prof. Dr. Max Mühlhäuser Dr. Guido Rößling"

Ähnliche Präsentationen


Google-Anzeigen