230 likes | 319 Views
16. Modularität und Abstraktion. von-Neumann-Rechner zunächst binär programmiert: Daraus wurde ein Oktal- oder Sedezimal-Code: Darin enthaltene Befehlscodes wurden durch Operator-Symbole vereinfacht: Anstelle der Speicheradressen traten symbolische Marken und Bezeichner:
E N D
16. Modularität und Abstraktion von-Neumann-Rechner zunächst binär programmiert: Daraus wurde ein Oktal- oder Sedezimal-Code: Darin enthaltene Befehlscodes wurden durch Operator-Symbole vereinfacht: Anstelle der Speicheradressen traten symbolische Marken und Bezeichner: Die Formulierung von Ausdrücken im Programm wurde zugelassen: ARGUM = ARGUM + 1 Durch Abstraktion von Berechnungsvorschriften entstanden Funktionen und Prozeduren 0000 1010 0000 0000 0011 1100 1000 1010 0000 0011 0000 0000 0000 0000 0000 0001 0000 1011 0000 0000 0011 1100 1000 1010 0A00 3C8A 0300 0001 0B00 3C8A LDA 3C8A AAD 0001 SPA 3C8A MARK1 = LDA ARGUM ADD 1 SPA ARGUM
Dienst-Modul in zwei Teile gegliedert: • Schnittstelle („Definition Module“) und • Implementierung („Implementation Module“). Verknüpfung von Modulen durch Importieren der Bezeichner aus Schnittstelle Vorteile: Abstraktion in Modula-2 • Schnittstellenprüfung ohne Kenntnis der Implementierung • Neuimplementierung der in der Schnittstelle deklarierten Prozeduren zieht keine Änderung des importierenden Moduls nach sich, auch keine Neuübersetzung. • Bei den Prozeduren ist eine höhere Abstraktion erreicht, den ihre Realisierung ist unsichtbar. • Die Bearbeitung und Übersetzung ist jeweils auf relativ kleine, überschaubare und leicht zu handhabende Einheiten begrenzt. • aussagekräftige Bezeichner • ausführliche Kommentare in den Schnittstellen, die die Funktion der Prozeduren beschreiben, auch für Fehler- und Sonderfälle. Nachteil: Konsistenz schwerer zu erreichen. Daher sind besonders wichtig:
Die Verwendung von Objekten aus anderen Modulen in einer Schnittstelle erfordert deren Import. Deren Gültigkeit beschränkt sich allerdings auf die Schnittstelle selbst. Werden diese Objekte auch in der Implementierung benutzt, so müssen sie dort erneut importiert werden. • Die erste Regel gilt nur für Importe aus anderen Modulen, nicht für Konstanten, Typen und Variablen, die in der zugehörigen Schnittstelle deklariert sind. Diese dürfen nicht in der Implementierung desselben Moduls erneut deklariert werden. Nur die Prozedurköpfe werden wiederholt. Ein zyklischer Import (Beispiel: die Schnittstelle eines Moduls M2 importiert aus Modul M1 und umgekehrt) zwischen Schnittstellen ist nicht möglich. Wohl erlaubt ist hingegen, daß die Implementierungen gegenseitig aus den Schnittstellen importieren. Import einzelner Objekte: FROM Modul1 IMPORT a,b,c; (* a,b und c muessen in der Schnittstelle von Modul1 deklariert sein *) Verwendung: ...; a := b + c; .... Import des ganzen Moduls: IMPORT Modul1; Verwendung einzelner Objekte: ...; Modul1.a := Modul1.b + Modul1.c; ... Modularisierung von Modula-2-Programmen
Modularisierung von Doppelkette Schnittstelle: DEFINITION MODULE EinfuegenUndLoeschen; (* Programm 1a *) TYPE ZeigTyp = POINTER TO ZeigRec; ZeigRec = RECORD Key : CARDINAL; Prev, Next : ZeigTyp; END (* RECORD *); VAR Anker : ZeigTyp; PROCEDURE Einfuegen (Nr: CARDINAL); (* fuegt neues Element Nr in die geordnete Liste ein. *) PROCEDURE Loeschen (Nr: CARDINAL); (* loescht Element Nr in Liste (muss vorhanden sein) *) END EinfuegenUndLoeschen.
Implementierung IMPLEMENTATION MODULE EinfuegenUndLoeschen; FROM Storage IMPORT (* PROC *) ALLOCATE, DEALLOCATE; PROCEDURE Einfuegen (Nr: CARDINAL); (* fuegt neues Element in die geordnete Liste ein *) VAR Hilf1 : ZeigTyp; BEGIN (* Einfuegen *) Hilf1 := Anker^.Next; WHILE (Hilf1^.Key < Nr) AND (Hilf1 <> Anker) DO Hilf1 := Hilf1^.Next; (* Nachfolger des neuen Elem. ...*) END (* WHILE *); (* ... oder Kettenende suchen *) WITH Hilf1^ DO (* der Nachfolger des neuen Elementes *) ALLOCATE (Prev^.Next, SIZE (ZeigRec)); (* neues Element einfuegen *) WITH Prev^.Next^ DO Key:= Nr; Next:= Hilf1; Prev:= Hilf1^.Prev; END (* WITH *); (* neues Element definiert *) Prev := Prev^.Next; (* neuen Vorgaenger anhaengen *) END (* WITH *); END Einfuegen;
PROCEDURE Loeschen (Nr: CARDINAL); (* loescht Element in Liste. Element mit Key = Nr muss vorhanden sein. *) VAR Hilf1 : ZeigTyp; BEGIN (* Loeschen *) Hilf1 := Anker^.Next; WHILE Hilf1^.Key # Nr DO Hilf1 := Hilf1^.Next; (* suchen *) END; WITH Hilf1^ DO Prev^.Next := Next; Next^.Prev := Prev; END (* WITH *); DEALLOCATE (Hilf1, SIZE (ZeigRec)); (* Element ist geloescht *) END Loeschen; BEGIN (* EinfuegenUndLoeschen *) (* Kette mit fliegendem Anker initialisieren *) ALLOCATE (Anker, SIZE (ZeigRec)); WITH Anker^ DO Key := 0; Next := Anker; Prev := Anker; END (* WITH *); END EinfuegenUndLoeschen.
Test-Modul MODULE DoppelKette; (* Programm 1c *) FROM InOut IMPORT (* PROC *) WriteCard, WriteLn; FROM EinfuegenUndLoeschen IMPORT (* TYPE *) ZeigTyp, ZeigRec, (* VAR *) Anker, (* PROC *) Einfuegen, Loeschen; PROCEDURE ZeigKette; VAR Hilf1 : ZeigTyp; BEGIN (* ZeigKette *) Hilf1 := Anker^.Next; WHILE Hilf1 # Anker DO WriteCard (Hilf1^.Key, 3); Hilf1 := Hilf1^.Next; END (* WHILE *); WriteLn; END ZeigKette; BEGIN (* DoppelKette *) Einfuegen(7); Einfuegen(1); Einfuegen(4); Einfuegen(5); Einfuegen(9); ZeigKette; Loeschen (1); Loeschen (9); Loeschen (5); ZeigKette; END DoppelKette.
Datenkapselung Bei vollständiger Datenkapselung darf keine Variable exportiert werden Kunden-Modul Dienst-Modul Import Anbindung an Dienstmodul durch Prozeduraufrufe Schnittstelle Konstanten- und Typdefinitionen Prozedurdeklarationen Implementierung VAR gekapselte Datenstruktur Prozedurdefinitionen interne Daten und Prozeduren BEGIN Initialisierung END
Schnittstelle DEFINITION MODULE KettenKapsel; (* Programm 2a *) (* Besser strukturierte Variante des Programms 1 *) PROCEDURE Einfuegen (Nr: CARDINAL); (* fuegt neues Element in die geordnete Liste ein. *) PROCEDURE Loeschen (Nr: CARDINAL); (* loescht Elem. in Liste. Element mit Key = Nr muss vorhanden sein *) PROCEDURE ZeigKette; (* zeigt den aktuellen Stand der Kette auf dem Bildschirm *) END KettenKapsel.
Implementierung IMPLEMENTATION MODULE KettenKapsel; (* Programm 2b *) FROM Storage IMPORT (* PROC *) ALLOCATE, DEALLOCATE; FROM InOut IMPORT (* PROC *) WriteCard, WriteLn; TYPE ZeigTyp = POINTER TO ZeigRec; TYPE ZeigRec = RECORD Key : CARDINAL; Prev, Next : ZeigTyp; END (* RECORD *); VAR Anker : ZeigTyp; PROCEDURE Einfuegen (Nr: CARDINAL); ... wie oben in Programm 1b ... END Einfuegen; PROCEDURE Loeschen (Nr: CARDINAL); ... wie oben in Programm 1b ... END Loeschen; PROCEDURE ZeigKette; ... wie oben in Programm 1c ... END ZeigKette; BEGIN (* KettenKapsel *) ... Initialisierung wie oben in Programm 1b ... END KettenKapsel.
ZeigTyp, ZeigRec und Anker sind private Informationen der Implementierung. Sie sind anderen Modulen nicht bekannt Vorteile: Datenstrukturen vor Fehlinterpretationen geschützt, Mißbrauch ist erschwert, gekapselte Datenstruktur kann ohne Auswirkung auf andere Module geändert werden Das Prinzip der Datenkapselung wurde 1972 von David Parnas unter der Bezeichnung information hiding eingeführt. Test-Modul MODULE DoppelKette; (* Programm 2c *) (* Version mit Kapselung der Daten *) FROM KettenKapsel IMPORT (* PROC *) Einfuegen, Loeschen, ZeigKette; BEGIN (* DoppelKette *) ... Hauptprogramm wie in Programm 1c ... END DoppelKette.
Beispiel Warteschlange DEFINITION MODULE SchlKapsel; (* Programm 4a *) (* Demonstr. Kapselung: FIFO-Speicher fuer CARDINAL-Zahlen *) PROCEDURE Bringen (Eintrag : CARDINAL); (* wenn istVoll vorher TRUE war, keine Wirkung; sonst wird Eintrag der Schlange zugefuegt *) PROCEDURE Holen (VAR Eintrag : CARDINAL); (* wenn istLeer vorher TRUE war, keine Wirkung; sonst wird Par. Eintrag mit dem aeltesten Eintrag der Schlange besetzt, und dieser wird aus der Schlange entfernt *) PROCEDURE istLeer (): BOOLEAN; (* TRUE, genau wenn Schlange kein Element enthaelt *) PROCEDURE istVoll (): BOOLEAN; (* TRUE, genau wenn Schlange kein Element mehr aufnehmen kann *) END Schlkapsel.
IMPLEMENTATION MODULE SchlKapsel; (* Programm 4b *) (* Demonstr. der Kapselung: FIFO-Speicher fuer CARDINAL-Zahlen Version mit Ringpuffer *) CONST MaxElemente = 100; Feldlaenge = MaxElemente+1; (* Minim. 1 Platz bleibt frei *) TYPE SchlIndTyp = [0 .. Feldlaenge-1]; VAR Schlange : ARRAY SchlIndTyp OF CARDINAL; (* die gekapselte *) AnfangVoll, AnfangLeer: SchlIndTyp; (* Datenstruktur *) PROCEDURE Nachfolger (Arg: SchlIndTyp) : SchlIndTyp; (* intern ,zyklische Nachfolgefunktion *) BEGIN (* Nachfolger *) RETURN (Arg + 1) MOD Feldlaenge END Nachfolger; PROCEDURE Bringen (Eintrag : CARDINAL); BEGIN (* Bringen *) IF NOT istVoll () THEN Schlange [AnfangLeer] := Eintrag; AnfangLeer := Nachfolger (AnfangLeer); END (* IF *); END Bringen; Implementierung
Implementierung, Fortsetzung PROCEDURE Holen (VAR Eintrag : CARDINAL); BEGIN (* Holen *) IF NOT istLeer () THEN Eintrag := Schlange [AnfangVoll]; AnfangVoll := Nachfolger (AnfangVoll); END (* IF *); END Holen; PROCEDURE istLeer () : BOOLEAN; BEGIN (* istLeer *) RETURN AnfangVoll = AnfangLeer END istLeer; PROCEDURE istVoll () : BOOLEAN; BEGIN (* istVoll *) RETURN Nachfolger (AnfangLeer) = AnfangVoll END istVoll; BEGIN (* Initialisierung *) AnfangVoll := 0; AnfangLeer := 0; END SchlKapsel.
Testmodul MODULE KapselTest; (* Programm 4c *) FROM SchlKapsel IMPORT (* PROC *) Bringen, Holen, istLeer, istVoll; FROM InOut IMPORT (* PROC *) ReadCard, Write, WriteLn, WriteCard, WriteString; PROCEDURE WriteB (Value: BOOLEAN); (* Ausgabe von Wahrheitswerten *) BEGIN (* WriteB *) IF Value THEN Write ('T'); ELSE Write ('F'); END; END WriteB; VAR Wert : CARDINAL; BEGIN (* KapselTest *) WriteString ('*** Start Test-Programm f. SchlKapsel ***'); WriteLn; WriteLn; WriteString ('Gib Zahl ohne Vorzeichen,'); WriteString (' 0 fuer Ausgabe, 999 fuer Ende'); WriteLn; WriteLn;
Testmodul, Fortsetzung LOOP (* jeweils Ein- oder Ausgabe eines Wertes *) Write ('>'); ReadCard (Wert); Write (' '); IF Wert = 999 THEN EXIT (* Abbruchbedingung *) ELSIF Wert = 0 THEN (* Wert aus Schlange holen *) IF istLeer () THEN WriteString (' Schlange ist leer'); ELSE Holen (Wert); WriteCard (Wert, 4); WriteString (' istLeer = '); WriteB (istLeer ()); END (* IF istLeer () *); ELSE (* Wert in Schlange setzen *) IF istVoll () THEN WriteString (' Schlange ist voll'); ELSE Bringen (Wert); WriteString (' istVoll = '); WriteB (istVoll ()); END (* IF istVoll () *); END (* IF *); WriteLn; END (* LOOP *); WriteString ('*** Ende Test ***'); WriteLn; END KapselTest.
Ein Datentyp ist ein Schema, aus dem Variablen gebildet werden können. Ein Abstrakter Datentyp ist ein Schema zur Bildung geschützter Variablen im Sinne der Kapselung. Er besteht aus • dem Namen des Typs, dessen innere Struktur nicht sichtbar ist, und • der Menge der zulässigen Operationen auf Objekten dieses Typs. Ein ADT ist nicht ein einzelnes Objekt, sondern ein Typ, von dem es beliebig viele Variablen geben kann. Die Initialisierung erfolgt jeweils einzeln. Abstrakte Datentypen (ADT) Anbindung an Dienstmodul durch Prozeduraufrufe Schnittstelle TYPE pT; (* privater Typ *) Prozedurdeklarationen (auch Initialisierungsprozedur) Import: privater Typ Prozeduren Kunden-Modul Implementierung TYPE pT = ...; Prozedurdefinitionen interne Daten und Prozeduren (* keine Initialisierung *) END
• Der Typ, auf den sich die Operationen des ADT beziehen, wird in der Schnittstelle deklariert, nicht aber definiert. Es handelt sich hierbei um einen opaken oder privaten Typ. Die Kunden-Module importieren den opaken Typ samt den Prozeduren und legen Variablen an. Die Initialisierung der Variablen bedarf einer eigenen Prozedur. Alle Operationen müssen durch das Modul ausgeführt werden, das den ADT exportiert. Die betreffenden Variablen werden als Parameter übergeben. Vorteile: Durch die Kapselung der Datenstrukturen sind Fehlinterpretationen vermindert und ist Mißbrauch erschwert. Die Datenstruktur ist ohne Auswirkungen auf andere Module änderbar. Die Zahl der Variablen, die aus einem ADT gebildet werden, bleibt offen. Nachteile: Die Datenzugriffe sind im allgemeinen etwas weniger effizient. Charakteristika von ADTs
Ein Stack (Keller) ist eine variable Datenstruktur zur Verwaltung von Elementen des gleichen Typs nach dem LIFO-Prinzip (last in first out). Anschaulich: lineare Liste, bei der Einfügen, Löschen und Abfrage nur an einem Ende ausgeführt werden. Definierte Operationen: • Push (Element): Einfügen eines neuen (dann obersten) Elementes, • Pop(): Entfernen des obersten Elementes, • Top(): Abfrage des obersten Elementes, • Empty(): Abfrage, ob der Stack leer ist. ADT Stack für CARDINAL
Schnittstelle DEFINITION MODULE ADTStack; (* Programm 6a *) (* Demonstration eines ADTs: LIFO-Speicher fuer CARDINAL-Zahlen *) TYPE StackTyp; (* ein privater (opaker) Typ *) PROCEDURE Push (VAR Stack : StackTyp; Eintrag : CARDINAL); (* Speichern des uebergebenen Wertes als oberstes Stackelement *) PROCEDURE Pop ( VAR Stack : StackTyp; VAR istLeer : BOOLEAN ) : CARDINAL; (* Abfrage und Entfernen des obersten Stackelementes: wenn istLeer FALSE, wird Wert des obersten Stackelements geliefert und Element geloescht; sonst keine Wirkung und Wert ist 0. *) PROCEDURE Top ( VAR Stack : StackTyp; VAR istLeer : BOOLEAN ) : CARDINAL; (* Abfrage des obersten Stackelementes:wenn istLeer FALSE, wird Wert des obersten Stackelementes geliefert, sonst 0. *) PROCEDURE Empty (Stack : StackTyp): BOOLEAN; (* TRUE, genau wenn der Stack leer ist *) PROCEDURE Initial (VAR Stack : StackTyp); (* Initialisierung des Stacks *) PROCEDURE Delete (VAR Stack : StackTyp); (* Loeschen des Stacks, d.h. Loeschen aller Elemente *) END ADTStack.
Implementierung IMPLEMENTATION MODULE ADTStack; (* Programm 6b *) (* Demonstration eines ADTs: LIFO-Speicher fuer CARDINAL-Zahlen *) FROM Storage IMPORT (* PROC *) ALLOCATE, DEALLOCATE; TYPE StackTyp = POINTER TO ElementTyp; ElementTyp = RECORD Wert : CARDINAL; Nachfolger : StackTyp; END (* RECORD *); PROCEDURE Push (VAR Stack : StackTyp; Eintrag : CARDINAL); VAR NeuElement : StackTyp; BEGIN (* Push *) (* Neuen Eintrag als 1. Element einfuegen *) ALLOCATE (NeuElement, SIZE (ElementTyp)); WITH NeuElement^ DO Wert := Eintrag; Nachfolger := Stack; END (* WITH *); Stack := NeuElement; END Push;
PROCEDURE Pop (VAR Stack : StackTyp; VAR istLeer : BOOLEAN ) : CARDINAL; VAR Eintrag : CARDINAL; Hilf : StackTyp; BEGIN (* Pop *) (* Erstes Element lesen und loeschen *) istLeer := Empty(Stack); IF istLeer THEN Eintrag := 0; ELSE Eintrag := Stack^.Wert; Hilf := Stack; Stack := Stack^.Nachfolger; DEALLOCATE (Hilf, SIZE (ElementTyp)); END; RETURN Eintrag END Pop; PROCEDURE Top (VAR Stack : StackTyp; VAR istLeer : BOOLEAN ) : CARDINAL; VAR Eintrag : CARDINAL; BEGIN (* Top *) (* Erstes Element lesen *) istLeer := Empty(Stack); IF istLeer THEN Eintrag := 0; ELSE Eintrag := Stack^.Wert; END; RETURN Eintrag END Top;
Implementierung, 3 PROCEDURE Empty (Stack : StackTyp): BOOLEAN; BEGIN (* Empty *) RETURN (Stack = NIL) END Empty; PROCEDURE Initial (VAR Stack : StackTyp); BEGIN (* Initial *) Stack := NIL; END Initial; PROCEDURE Delete (VAR Stack : StackTyp); VAR Hilf : StackTyp; BEGIN (* Delete *) WHILE Stack # NIL DO Hilf := Stack; Stack := Stack^.Nachfolger; DEALLOCATE (Hilf, SIZE (ElementTyp)); END (* WHILE *); END Delete; END ADTStack.