1.2 Explizite Schnittstellenbeschreibung
Transcription
1.2 Explizite Schnittstellenbeschreibung
1.2 Explizite Schnittstellenbeschreibung Verschiedene Programmiersprachen verfügen über ein eigenes Konstrukt zur Formulierung von Schnittstellen. alp3-1.2 1 1.2.1 Schnittstellen in Java Feststellungen zur Schnittstelle von Queue: $ Die aus den Operationen append, remove, length bestehende Schnittstelle muss aus dem Klassen-Text „herausgesucht“ werden. $ Die Klasse kapselt zwar die Implementierung, verbirgt sie aber nicht vor dem menschlichen Leser. $ Die Klasse ist für die Benutzung von Queue-Objekten gar nicht erforderlich - nur für die Objekterzeugung ! Daher: alp3-1.2 2 Explizite Schnittstellenbeschreibung interface I {...} $ beinhaltet lediglich die Köpfe aufrufbarer Methoden, z.B. void m(); $ ist (Verweis-)Typ – ähnlich wie class C {...} – $ und damit zur Typisierung von Variablen/Parametern verwendbar, z.B. void op(I x) { ... x.m(); ... } Essential der Objektorientierung Objektzugriff sollte über eine Variable erfolgen, die lediglich mit einem Schnittstellentyp vereinbart ist. „Programmiert wird gegen Schnittstellen, nicht gegen Klassen.“ alp3-1.2 3 Beispiel: interface Queue { void append(Item i) throws QueueOverflow; Item remove() throws QueueUnderflow; int length(); int max = 100; } Syntax: InterfaceDeclaration: { Modifier } interface Name InterfaceBody InterfaceBody: { { { Modifier } Declaration } } Einschränkungen für Declaration : - Methoden müssen leeren Rumpf haben: ; - als Attribute nur Konstantenvereinbarungen - keine Konstruktoren und Initialisierer alp3-1.2 4 $ Vereinbarungen in einer Schnittstelle sind stets public, auch wenn nicht explizit angegeben. $ Eine Attributvereinbarung in einer Schnittstelle ist stets static und final, auch wenn nicht explizit angegeben, und muss mit einem Wert belegt sein (konstanter Ausdruck). Daher ist z.B. ist Queue.max ein legitimer Ausdruck. $ ! Auch Klassen und Schnittstellen dürfen in einer Schnittstelle vereinbart werden. Umgekehrt ist eine Attributschnittstelle, d.h. eine Schnittstelle, die in einer Klasse oder Schnittstelle vereinbart wird, stets implizit static, ist also immer globale Schnittstelle (analog zu geschachtelten globalen Klassen, 1.1.2.1Å) alp3-1.2 5 Bindung von Klassen an Schnittstellen: Eine Klasse kann als Implementierung einer Schnittstelle vereinbart werden: public class ArrayQueue implements Queue { private Item[] cell = new Item[max]; private int front, length; public void append(Item i) throws QueueOverflow { if(length == cell.length) throw new QueueOverflow(this); cell[(front+length++)%cell.length] = i; } public Item remove() throws QueueUnderflow {... } public int length() { return length; } } alp3-1.2 6 ... und zusätzlich z.B. public class LinkedQueue implements Queue { // unlimited capacity private class Cell {Item value; Cell next; Cell(Item i){value = i;} } private Cell front, rear; public void append(Item i) { // no overflow Cell c = new Cell(i); if(front==null) front = c; else rear.next = c; rear = c; } public void insert(Item i) {... } public Item remove() throws QueueUnderflow {... } public int length() {... } } alp3-1.2 7 UML: alp3-1.2 8 Pflichten der Klasse: $ Jede in der Schnittstelle postulierte Operation muss als vollständig implementierte Methode vereinbart werden (Ausnahme: abstrakte Klasse ¼2.2.4) . $ Ihr Ergebnistyp darf dabei geändert werden, muss aber verträglich mit dem Ergebnistyp in der Schnittstelle sein. Es dürfen nicht mehr - wohl aber weniger - Ausnahmen vereinbart werden. Die Argumentnamen dürfen beliebig geändert werden. $ Die implementierten Methoden müssen als public vereinbart werden. alp3-1.2 9 Rechte der Klasse: $ Die anderen Vereinbarungen der Schnittstelle sind in der Klasse sichtbar und können so verwendet werden, als seien sie in der Klasse vereinbart. $ Zusätzlich können weitere Eigenschaften nach Belieben vereinbart werden (auch mit Überladen). alp3-1.2 10 Eine Klasse kann auch mehrere Schnittstellen implementieren: interface PhoneBook { class Entry {String name; long number; Entry next; public Entry(String s, long i){ name = s; number = i; } } void enter(Entry e); long lookup(String name) throws NoSuchName; } interface Printable { String toString(); } alp3-1.2 public class PhoneBookImpl implements PhoneBook, Printable { ..... } Namenskollisionen? ¼2.2.4 11 public class PhoneBookImpl implements PhoneBook, Printable { private Entry first; public void enter(Entry e) {..... } public int lookup(String name) {..... } // delivers 0 if no such name public int length() {..... } void other() {..... } public String toString() {..... } }  new PhoneBookImpl().enter(new PhoneBook.Entry(x,i)) (Alternative: private class Entry in der Klasse) alp3-1.2 12 1.2.2 Typverträglichkeit Wie bereits betont: Schnittstellen sind auch Typen, genauer: Verweistypen (reference types) (wie Klassen auch): Type: PrimitiveType ArrayType ClassType InterfaceType InterfaceType: Identifier Beispiel Queue: alp3-1.2 Es gibt Queue-Variable, -Parameter, -Ergebnisse - aber keine Queue-Objekte ! 13 Typverträglichkeit: Wenn die Klasse K die Schnittstelle S implementiert, ist der Klassentyp K mit dem Schnittstellentyp S verträglich. "K ist Untertyp von S." ArrayQueue q1 = new ArrayQueue(); ..... Queue q2 = new LinkedQueue(); q2.append(q1.remove()); boolean b = q2 instanceof LinkedQueue; // true q2 = q1; // makes q2 refer to ArrayQueue LinkedQueue q3 = q1; // type error q1 = q2; q1 = (ArrayQueue) q2; q3 = (LinkedQueue) q2; // // // // q2.insert(x); // ((LinkedQueue)q2).insert(x);// alp3-1.2 type error cast ok cast ok, but: throws ClassCastException type error successful cast 14 Def.: Statischer Typ von Variable/Parameter mit Verweistyp = vereinbarter Variablen- bzw. Parametertyp Dynamischer Typ von Variable/Parameter mit Verweistyp = (Klassen-)Typ des Objekts, auf das im Augenblick verwiesen wird alp3-1.2 15 Def.: Statischer Typ von Variable/Parameter mit Verweistyp = vereinbarter Variablen- bzw. Parametertyp Dynamischer Typ von Variable/Parameter mit Verweistyp = (Klassen-)Typ des Objekts, auf das im Augenblick verwiesen wird q2 (Queue) LinkedQueue q3 (LinkedQueue) alp3-1.2 16 Zusammenfassend: Guter Stil: für nichttriviale Klassen stets explizite Schnittstellen vorsehen als Typbezeichnungen in der Regel Schnittstellentypen verwenden Klassentypen nur bei der Objekterzeugung mit new verwenden  Effekt: Der Benutzer eines Objekts weiß grundsätzlich nichts über die dahinterstehende Klasse/Implementierung (kennt nicht einmal deren Namen!), sondern kennt nur die Schnittstelle. static void rotate(Queue q) { try { if(q.length()!=0) q.append(q.remove()); } catch(Exception e) {} } alp3-1.2 17 1.2.3 Schnittstellentypen verallgemeinern Prozedurtypen Schnittstellentypen verallgemeinern die z.B. aus Pascal/Modula/C/... bekannten Prozedurtypen ! Beispiel: Methode zur Approximation einer zwischen a und b liegenden Nullstelle einer Funktion mittels regula falsi: static float zero(Function f, float a, float b) { float fofa = f.of(a); float fofb = f.of(b); ... } interface Function { float of(float x); } alp3-1.2 18 Anwendung z.B. so: class XsquareMinus2 implements Function { public float of(float x) { return x*x-2; } } // diese Klasse hat keine Attribute! zero(new XsquareMinus2(),1,2) liefert 1,414... alp3-1.2 19 Zur Erinnerung - mit Prozedurtyp function in Modula: TYPE function = PROCEDURE(REAL): REAL; PROCEDURE zero(f: function; a,b: REAL): REAL; VAR fofa, fofb: REAL; ... BEGIN fofa := f(a); fofb := f(b); ..... END; PROCEDURE xsquareMinus2(x: REAL): REAL; BEGIN RETURN x*x-2.0 END; zero(xsquareMinus2,1,2) alp3-1.2 20 1.2.4 Anonyme Klassen Anonyme Klasse (anonymous class) ist Variante einer lokale Klasse (1.1.2.2Å), die nur für ein ad-hoc erzeugtes, lokales Objekt benötigt wird: bei der Objekterzeugung wird direkt der Klassenrumpf angegeben - Klassenkopf und Konstruktoren fehlen. Beispiel: zero(new Function(){public float of(float x) {return x*x-2;}}, 1,2); (Vgl. Lambda-Ausdruck in alp3-1.2 zero (\x->x*x-2) 1 2 ) 21 1.2.5 Entwurfsmuster „Abstrakte Fabrik“ Beachte: Eine Schnittstelle enthält keine Konstruktoren. Deren Signaturen werden erst durch die implementierenden Klassen festgelegt - und können von Klasse zu Klasse verschieden sein. Nachteil: Bei der Objekterzeugung muss nicht nur eine bestimmte Klasse benannt werden (was in der Natur der Sache liegt) - man muss auch ihre spezifischen Konstruktoren in Erfahrung bringen. alp3-1.2 22 Abhilfe bringt beispielsweise die folgende Konvention: Anstelle von expliziten Konstruktoren werden Operationen zur Initialisierung vorgesehen die zur Schnittstelle gehören. interface Queue { // mit 2 „Konstruktoren“ void init(int max); // capacity at least max void init(); // capacity at least 100 Item remove() throws QueueUnderflow; ... } Queue q = new SomeQueue(); q.init(50); Aber: Evtl. unsichere Programmierung! alp3-1.2 23 Besser: Schnittstelle Abstrakte Fabrik (abstract factory) verbirgt konkrete „Fabrikobjekte“ mit Operationen zum Erzeugen von Objekten einer „Produktfamilie“ interface Factory { // for queues/stacks of strings Queue createQueue(int max); // capacity at least max Stack createStack(); // capacity at least 100 } static void test(Factory f) { Queue q = f.createQueue(50); q.append("first"); Stack s = f.createStack(); s.push(""); ... Die hier erzeugten Objekte gehören zur gleichen Produktfamilie - aber der Aufrufer von test bestimmt, zu welcher: alp3-1.2 24 z.B. Produktfamilie „Geflechte ohne Längenbeschränkung“: public class LinkedFactory implements Factory { private static class Cell {.....} // 1.2.1 public Queue createQueue(int max) { return new Queue(){Cell front,rear;...}; // 1.2.1 } public Stack createStack() { return new Stack(){Cell top; ...}; } } ..... test(new LinkedFactory()); test(new ArrayFactory(1000); alp3-1.2 25 Entwurfsmuster (design pattern) „abstrakte Fabrik“: alp3-1.2 26 1.2.6 Schnittstellen in Modula Zur Erinnerung (1.1.3.1): MODULE Queues; IMPORT ... EXPORT queue,... CONST ... TYPE ... PROCEDURE ... ... END Queues; = Implementierung, deren Schnittstelle durch EXPORT angegeben ist (vergleichbar einer Klasse mit public Eigenschaften) alp3-1.2 27 Explizit angegebene Schnittstelle. DEFINITION MODULE Queues; FROM Items IMPORT Item; CONST max = 100; TYPE queue; PROCEDURE create(): queue; PROCEDURE append(i: Item; q: queue); PROCEDURE remove(q: queue): Item; PROCEDURE length(q: queue): INTEGER; END Queues. Implementierung dazu: IMPLEMENTATION MODULE Queues; TYPE queue = POINTER TO Qrep; ... // Code aus 1.1.3.1 END Queues. alp3-1.2 28 Gegenüberstellung: klassenbasiert modulbasiert interface I DEFINITION MODULE Ts TYPE t; ... class A implements I IMPLEMENTATION MODULE Ts TYPE t = ... class B implements I class C implements I ..... new A() alp3-1.2 newT() 29 1.2.7 Schnittstellen in Haskell Typklasse (type class): benannte Menge von Signaturen (entspricht dem Java interface !) class Queue queue where emptyq :: queue t append :: (t, queue t) -> queue t remove :: queue t -> (t, queue t) isEmpty:: queue t -> Bool Queue ist der Schnittstellen-Name queue ist eine „Typvariable“, die als Stellvertreter für beliebige konkrete (hier polymorphe!) Repräsentationen steht alp3-1.2 30 Exemplar (instance) einer Typklasse ist ein konkreter Typ mit zugehörigen Implementierungen der Signaturen (entspricht zwei Java-Klassen, einer „Daten-Klasse“ und einer „Methoden-Klasse“!) data StandardQueue t = Srep[t] instance Queue StandardQueue emptyq = Srep[] append(x, Srep q) = remove(Srep(x:q)) = remove _ = isEmpty(Srep q) = where Srep(q++[x]) (x, Srep q) error "underflow" null q Dies erklärt StandardQueue zum "Exemplar der Typklasse" Queue, also zur Implementierung der Schnittstelle Queue. alp3-1.2 31 Ein anderes Exemplar z.B. data BatchedQueue t = Brep[t][t] instance Queue BatchedQueue where emptyq = Brep[][] append(x, Brep[]_) = Brep[x][] append(x, Brep front rear) = Brep front(x:rear) remove(Brep[]_) = error "queue underflow" remove(Brep[x]rear) = (x, Brep(reverse rear)[]) remove(Brep(x:front)rear) = (x, Brep front rear) isEmpty(Brep front rear) = null front Man sagt auch „StandardQueue und BatchedQueue gehören zur Typklasse Queue“. alp3-1.2 32 Benutzung: z.B. rotate q = if isEmpty q then q else append(remove q) ist unabhängig vom konkreten Typ von q - ist aber nur für solche Typen benutzbar, die zur Typklasse Queue gehören: Die Signatur enthält die Kontextangabe Queue a => : rotate :: Queue a => a t -> a t Dies ist ein eingeschränkt polymorpher Typ. ( Vergleiche dagegen [1.1.3.2Å] rotate :: Queue a -> Queue a wobei Queue Teil einer Modul-Schnittstelle war! ) alp3-1.2 33 Damit interaktiv isEmpty(rotate(Srep[]))  True isEmpty(rotate(Brep[][]))  True isEmpty(rotate[])  ERROR - Illegal Haskell 98 class constraint ... alp3-1.2 34 Beachte: $ keine zustandsbehafteten Objekte $ explizite Schnittstellenbeschreibung $ Koexistenz verschiedener Implementierungen / keine Datenabstraktion Datenabstraktion wird durch Verwendung von Modulen erzielt (1.1.3.2Å), z.B. je ein Modul für jede Implementierung und ein gemeinsam benutztes Modul für die Schnittstelle: module Queues(Queue(..)) where class Queue queue where ..... -- end Queues alp3-1.2 35 module StandardQueues(StandardQueue,newq) where import Queues data StandardQueue t = Srep[t] newq = Srep[] instance Queue StandardQueue where ... -- end StandardQueues module BatchedQueues(BatchedQueue,newq) where import Queues data BatchedQueue t = Brep[t][t] newq = Brep[][] instance Queue BatchedQueue where ... -- end BatchedQueues alp3-1.2 36 module import import import QueueTest where Queues qualified StandardQueues qualified BatchedQueues newS = StandardQueues.newq newB = BatchedQueues.newq rotate q = if isEmpty q then q else append(remove q) rotate :: Queue a => a t -> a t // wie S. 33 cat q p = if isEmpty p then q else cat(append(first,q))rest where (first,rest) = remove p cat :: (Queue a, Queue b) => a t -> b t -> a t // ! -- end QueueTest Damit interaktiv z.B. isEmpty(cat newS(append("",newB))) alp3-1.2  False 37 Ein Typ kann nach Belieben als Exemplar mehrerer Typklassen vereinbart werden - damit hat er dann mehrere Schnittstellen. class Functor typ where -- aus Haskell-Bibliothek, polymorph! fmap :: (a->b) -> typ a -> typ b instance Functor StandardQueue where fmap f (Srep q) = Srep(map f q) instance Functor BatchedQueue where fmap f (Brep p q) = Brep(map f p)(map f q) Damit z.B. fmap head (append("Max",append("Fritz",news))) liefert Schlange mit den Elementen 'F' und 'M' . alp3-1.2 38 Aber interaktive Eingabe von fmap head (append("Max",append("Fritz",news))) liefert ERROR - Cannot find "show" function for: *** Expression : fmap head (append ("Max",append .... *** Of type : StandardQueue Char Zwei Lösungs-Alternativen: n module StandardQueues(StandardQueue, newq) where import Queues data StandardQueue t = Srep[t] deriving Show ... liefert Ausgabe alp3-1.2 Srep "FM" 39 o module StandardQueues(StandardQueue, newq) where import Queues data StandardQueue t = Srep[t] ... instance Show t => Show (StandardQueue t) where show(Srep q) = "Schlange " ++ show q liefert Ausgabe alp3-1.2 Schlange "FM" 40 Informationsabfrage in interaktivem Haskell (z.B. Hugs): :info StandardQueue -- type constructor data StandardQueue a -- constructors: Srep :: [a] -> StandardQueue a -- instances: instance Queue StandardQueue instance Functor StandardQueue instance Show a => Show (StandardQueue a) alp3-1.2 41