Programmierparadigmen: a) funktionale Programmierung: Programm: Funktion Berechnung: Reduktion b) logische Programmierung: Programm: Relationen / Menge von Hornklauseln Berechnung: Deduktion c) Objektorientertes Programmierung: Programm: Menge von Klassen und Objekten Berechnung: Austausch von Nachrichten zwischen Objekten Objekte kapseln _Zustand_ (Daten) und _Verhalten_ (Operationen) ein. Beispiel eines Objekt's in ML: signature PERSON = sig type T val new : unit -> T val getAge : T -> int val getName : T -> string val setAge : T * int -> unit val setName : T * string -> unit val printString : T -> string end structure Person:PERSON = struct type T = {age: int ref, name: string ref} fun new p = {age = ref 0, name = ref ""} fun getAge (p:T) = !(#age p) fun getName (p:T) = !(#name p) fun setAge (p:T,i) = #age p := i fun setName (p:T,n) = #name p := n fun printString p = (getName p) ^ "(" ^ (makestring (getAge p)) ^ ")" end val p = Person.new (); Person.setName (p,"Hubert"); Person.setAge (p,32); Person.getName p; Person.getAge p; print (Person.printString p); Objekte haben Zustand: Person.setAge(p,33); Person.getAge p; Instanzvariablen: age und name ---------------------------------------- signature STUDENT = sig type T val new : unit -> T val getAge : T -> int val getName : T -> string val setAge : T * int -> unit val setName : T * string -> unit val getMatrNr : T -> string val setMatrNr : T * string -> unit val printString : T -> string end structure Student:STUDENT = struct type T = {age: int ref, name: string ref, mat: string ref} fun new p = {age = ref 0, name = ref "",mat=ref ""} fun getAge (p:T) = !(#age p) fun getName (p:T) = !(#name p) fun setAge (p:T,i) = #age p := i fun setName (p:T,n) = #name p := n fun getMatrNr (p:T) = !(#mat p) fun setMatrNr (p:T,n) = #mat p := n fun printString p = (getMatrNr p) ^ ": " ^ (getName p) ^ "(" ^ (makestring (getAge p)) ^ ")" end Ziel: Man m"ochte eine gemischte Liste von Studenten und Personen drucken. Naive L"osung printAll (* doList : ('a -> 'b) -> 'a list -> unit *) fun doList f [] = () | doList f (h::t) = (f h;(doList f t)) val printAll = doList (fn p => (print (printString p); print "\n")) Zwei Probleme: 1) Eine 'a list kann entweder Studenten oder Personen enthalten, aber nicht Studenten und Personen gleichzeitig. 2) Welche Funktion printString soll genommen werden, Person.printString oder Student.printString? ML L"osung: =========== datatype PuS = person of Person.T | student of Student.T val printAll = doList (fn (person p) => (print (Person.printString p); print "\n") | (student p) => (print (Student.printString p); print "\n")) Nachteil: Soll noch ein weiterer Personentyp hinzugef"ugt werden, mu"s der Typ PuS und die Funktion printAll angepa"st werden. L"osung mit "uberladenen Operatoren =================================== printString k"onnte wie + und * "uberlanden sein, d.h. printString : Person.T -> unit printString : Student.T -> unit (dies ist in ML nicht m"oglich) Nachteil: der Compiler mu"s beim "Ubersetzen wissen welche der beiden Funktionen einzusetzen ist. Beispiel: fun sqr a = a * a gibt eine Fehlermeldung. statisches Binden: Beim "Ubersetzen wird entschieden welche Funktion zu verwenden ist. Konsequenz: Die Liste kann nur Personen oder Studenten enthalten, aber nicht beide gleichzeitig (unabh"angig von dem Typproblem). L"osung mit "uberladenen Operatoren und dynamischen Binden ========================================================== Dynamisches Binden: Es wird erst zur Laufzeit entschieden welche printString Funktion verwendet wird. Welche Funktion verwendet wird h"angt von dem Argument der Funktion ab. Bei mehreren Argumenten: Von welchem Argument h"angt die Funktionsauswahl ab? "Ublich: vom Ersten. Deshalb eine neue Sichtweise: Nachricht: Aufforderung an den Empf"anger etwas zu tun. Methode: Programmcode, das der Empf"anger nach Erhalt einer Nachricht ausf"uhrt. Anstatt 'printString p' wird nun 'p printString' geschrieben. printString ist die Nachricht, die weiter Argumente enthalten kann, p der Empf"anger der Nachricht, der dann entscheidet, wie er auf die Nachricht reagieren soll, indem er eine Methode aufruft, z.B. Person.printString oder Student.printString. Objekt = Instanzvariablen (Zustand) + Methoden (Verhalten) Das Verhalten eines Objekts sind seine Reaktionen auf Nachrichten, die durch die Methoden eines Objekts bestimmt sind. Das sind: - Vesenden weiterer Nachrichten - Zustands"anderung - primitive Operationen (Erzeugen von neuen Objekten, Arithmetik) Wichtig: Der Zustand von Objekten kann nur durch Versenden von Nachrichten von au"sen eingesehen oder ge"andert werden. (information hiding) Beispiel: Smalltalkklasse Object subclass: #Person instanceVariableNames: 'age name ' classVariableNames: '' poolDictionaries: '' category: 'Vorlesung' age ^age name ^name age: i age := i name: n name := n printString ^self name , '(' , self age printString , ')' printAll in Smalltalk --------------------- | c | c := OrderedCollection new. "Entspricht in etwa ML Listen" "Der Programmteil, der printAll entspricht." c do: [:p| Transcript show: p printString. Transcript cr] Das erste Problem ist noch nicht gel"ost: Studenten und Personen k"onnen nicht gemeinsam in einer Liste sein. L"osung in Smalltalk: Variablen und Funktionen sind ungetypt und k"onnen beliebige Objekte enthalten. Nachteil: Man kann zu c Objekte hinzuf"ugen, die die Nachricht printString nicht verstehen. Das Typsystem von ML verhindert so eine Situtation in ML. Was passiert in Smalltalk wenn eine Nachricht nicht verstanden wird? Es wird eine neue Nachricht doesNotUnderstand: an das Objekt geschickt, das die Nachricht urspr"unglich nicht verstanden hat. Das Argument zu doesNotUnderstand: ist die Nachricht, die nicht verstanden wurde. Jedes Objekt sollte eine Methode f"ur die Nachricht doesNotUnderstand: haben. In der Regel erzeugt diese Methode eine Ausnahme (Exception). Aber es gibt auch andere L"osungen f"ur dieses Problem: Eine Erweiterung des Typsystems um Subtypen. Da ein Student eine Spezialisierung von Person ist (versteht alle Nachrichten, die eine Person versteht plus zus"atzliche Nachrichten) kann eine Student an jeder Stelle eingesetzt werden, an dem eine Person verlangt wird. Dies ergibt eine Ordnung eine Ordnung < auf Typen. Student.T < Person.T. Dann kann eine 'a list Objekte vom Typ 'a und 'b < 'a enthalten. Da Objekte vom Typ 'b alle Nachrichten verstehen, die Objekte vom Typ 'a verstehen, kann der Typchecker davon ausgehen, da"s er es nur mit Objekten vom Typ 'b zu tun hat. Klassen ------- Ein Personenobjekt ist vergleichbar mit einem Wert in Person.T. Eine Klasse ist vergleichbar mit der Struktur Person:PERSON. In Smalltalk sind dies wieder Objekte mit Zustand (Instanzvariablen) und Verhalten (Methoden). Klassen speichern den Namen der Instanzvariabeln von Objekten (ihren Instanzen) und die Methodendefinitionen in den geeigneten `Instanzvariablen der Klasse'. F"ur alle Objekte einer Klasse wird der Code f"ur die Methoden nur einmal gespeichert. Wenn ein Objekt eine Nachricht bekommt wird die zur Nachricht passende Methode in dem Methodenverzeichnis der Klasse gesucht und in dem Kontext des Objektes ausgef"uhrt, da jedes Objekt seine eigenen Instanzvariablen hat.