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