7. Programmeinheiten
 
zurück
7.11.3 Kind-Pakete


Syntaktisch wird ein Paket zu einem Kind-Paket erklärt, indem der Name des Kind-Pakets mittels "." (Punkt) an den Namen des bereits existierenden Vater-Pakets angefügt wird.

package ein_paket is -- irgendein Paket
   -- ...
end ein_paket;

package ein_paket.kind is -- Das Paket "kind" wird zu einem Kind-Paket
   -- ... -- von "ein_paket" erklärt. Damit wird dieses zum Vater-Paket.
end ein_paket.kind;

Applikationen lassen sich oft in logische Einheiten unterteilen, die zusammen eine gemeinsamen Aufgabe lösen. Können diese logischen Einheiten (unter Beibehaltung der logischen Struktur) auch in programmiertechnische Einheiten überführt werden, so vereinfacht sich das Erstellen komplexer und umfangreicher Applikationen.

Ada bietet die Möglichkeit, solche logischen Einheiten in eigenständigen Paketen mittels des Konzeptes von sogenannten Kind-Paketen zusammenzufassen. So bleibt der Zusammenhang gewahrt bei gleichzeitigem Zugriff aller Kind-Pakete untereinander. Das Ergebnis sind hierarchische Bibliotheken.

Das Paket an oberster Stelle der Hierarchie wird Vater oder auch Wurzelpaket genannt, untergeordnete Pakete, Kind-Pakete und Kind-Pakete untereinander werden Geschwister-Pakete genannt. Insgesamt ergibt sich eine Hierarchie von Paketen, kurz Pakethierarchie.

Vorhandene Applikationen, die nicht verändert werden sollen oder nicht verändert werden können, lassen sich durch Anfügen von Kind-Paketen erweitern. Solche Erweiterungen beeinträchtigen die Zuverlässigkeit der vorhandenen Applikation nicht, nur ihr Funktionsumfang wird vergrößert. Pflege und Änderung lassen sich so mit geringerem Aufwand betreiben.

Der entscheidende Vorteil bei diesem Konzept ist, daß das Kind-Paket vollen Zugriff auf den privaten Teil der Spezifikation seines Vaters hat. Dieser Zugriff ist allerdings so eingeschränkt, daß das Kind-Paket weder direkt noch indirekt den privaten Teil seines Vaters offenlegen kann.


Kind-Pakete erlauben es, Applikationen - a priori (von vornherein) zu strukturieren und - a posteriori (im nachhinein) wiederzuverwenden.

Die a priori - Strukturierung

Applikationen zeichnen sich oft durch eine Vielzahl von logisch unterschiedlichen Sub-komponenten aus, die in ihrer Gesamtheit die Implementierung bilden. Diese Subkomponenten gruppieren Funktionen und Prozeduren, so daß sich die Struktur der Applikation einfacher überblicken läßt.

In Ada lassen sich solche Subkomponenten mittels Kind-Paketen verwirklichen.


Beispiel:

Ein gerichteter Graph besteht aus Ecken und Kanten. Die Kanten weisen von einer Ecke zu einer anderen. Ein solcher gerichteter Graph kann in folgende Pakete zerlegt werden:
  • Konstruktoren
  • Iteratoren
  • Selektoren
Das Paket, das die Konstruktoren enthält, wird zum Vater der beiden Kind-Pakete Iteratoren und Selektoren. -- Spezifikation des Vater-Pakets. Es enthält die Konstruktoren.

package gerichteter_graph is
   type graph is limited private;
   type kante is private;
   type ecke is private;

   -- Entfernt alle Ecken und Kanten und gibt einen leeren Graphen zurueck.
   procedure entleere( den_graph : in out graph );

   -- Kopiert alle Ecken und Kanten.
   procedure kopiere( den_graph : in graph; nach : in out graph );

   -- Fuegt eine neue Ecke zum Graphen.
   procedure fuege_hinzu( die_kante : in kante; zum_graphen : in out graph );

   -- Verbinde zwei Ecken mit einer Kante
   procedure verbinde( die_kante : in out kante;
                       von_ecke  : in ecke;
                       bis_ecke  : in ecke );

   -- ...

private
   -- Die Typen "graph", "kante" und "ecke" sind abhängig von der Implementierung.
end gerichteter_graph;

-- Spezifikation eines Kind-Pakets. Es enthält die Selektoren.
package gerichteter_graph.selektoren is
   -- Liegt ein leerer Graph vor.
   function ist_leer( der_graph : in graph ) return boolean;

    -- Wieviele Ecken enthält der Graph.
    function zaehle_ecken( vom_graph : in graph ) return natural;

    -- Wieviele Kanten enthält der Graph.
    function zaehle_kanten( vom_graph : in graph ) return natural;

    -- Enthält der Graph folgende Ecke.
    function ist_teil( die_ecke : in ecke; der_graph : in graph ) return boolean;

    -- Enthält der Graph folgende Kante.
    function ist_teil( die_kante : in kante; der_graph : in graph ) return boolean;

    -- Welchen Ursprung hat die Kante.
    function gib_ursprung( der_kante : in kante ) return ecke;

    -- Welches Ziel hat die Kante.
    function gib_ziel( der_kante : in kante ) return ecke;

    -- ...
end gerichteter_graph.selektoren;

-- Spezifikation eines Kind-Pakets. Es enthält die Iteratoren.
package gerichteter_graph.iteratoren is
   -- Positioniert den Graph so, daß die nächste die erste Ecke ist.
   procedure setze_ecke_auf_start( der_graph : in out graph );

   -- Hole nächste Ecke.
   function hole_naechste_ecke( vom_graph : in out graph ) return ecke;

   -- Positioniert den Graph so, daß die nächste die erste Kante ist.
   procedure setze_kante_auf_start( der_graph : in out graph );

   -- Hole nächste Kante.
   function hole_naechste_kante( vom_graph : in out graph) return kante;

   -- ...
end gerichteter_graph.iteratoren;

Beide Kind-Pakete haben Zugriff auf den privaten Teil der Spezifikation von "gerichter_graph" und damit auf die Implementierung der als privat deklarierten Typen "graph", "ecke" und "kante".

Für diese Implementierung eines gerichteten Graphen soll keine Ecke als erste ausgezeichnet sein, wichtig ist nur, daß mit den Iteratoren alle Ecken und Kanten angesteuert werden. Daher ist auch der Parametermodus "in out", denn der Graph muß sich merken, welche Ecke die letzte war bzw. welche schon ausgegeben wurden.

Die a priori - Addition

Das in "A-priori-Strukturierung" angegebene Beispiel eines gerichteten Graphen führt nur die Basisfunktionen und -prozeduren auf, die notwendig sind, um auf den drei privaten Typen "graph", "ecke" und "kante" zu operieren. Für bestimmte Anforderungen an gerichtete Graphen können allerdings weitere Funktionen notwendig werden, je nachdem, was mittels eines gerichteten Graphen realisiert werden soll. Diese können durch einfaches Hinzufügen weiterer Kind-Pakete implementiert werden.

Beispiel:

Für zwei Ecken aus einem gerichteten Graphen ist nicht unerheblich zu wissen, welches der kürzeste bzw. längste Weg (ohne eine Ecke doppelt zu enthalten) von einer Ecke zu einer anderen ist. Für das Versenden von Briefen oder Paketen sind kürzeste Wege sinnvoll. Bei anderen Anwendungen ist wichtig zu wissen, (gleichgültig welchen Weg die Nachricht -durch den Graphen nimmt- nimmt), über wieviele Ecken die Nachricht maximal laufen kann. Das bestimmt die maximale Verweildauer einer Nachricht. Dazu muß die Anzahl der Ecken des längsten Weges bestimmt werden.

Solche Anforderungen lassen sich mittels Kind-Paketen an bestehende Pakete anfügen, ohne daß diese verändert und damit wieder getestet werden müssen. Das bestehende Paket benötigt keine Informationen (es braucht nicht verändert zu werden) über das Kind-Paket. Das bestehende Paket "weiß" überhaupt nicht, daß ein Paket als Kind-Paket angefügt wird. Es kann auch weiterhin für sich alleine genutzt werden.

package gerichteter_graph.wege is
   -- Berechnet den kuerzesten Weg von einer Ecke zu einer anderen.
   function kuerzester_weg( von : in ecke; bis : in ecke )return graph;

   -- Berechnet den laengten Weg von einer Ecke zu einer anderen.
   function laengster_weg( von : in ecke; bis : in ecke ) return graph;

end gerichteter_graph.wege;

In diesem Beispiel wird nicht berücksichtigt, daß es mehrere kürzeste bzw. längste Wege geben kann. Es sei ausreichend, einen zu kennen.
Natürlich ist es auch möglich, schon bestehende Kind-Pakete zu erweitern.

Beispiel:

Die Frage, ob ein Graph einen anderen als Subgraph enthält, kann durch ein Kind-Paket zu "gerichteter_graph.selektoren" beantwortet werden.

package gerichteter_graph.selektoren.sub_graph is
    -- true : falls ‘sub’ im ‘der_graph’ enthalten ist.
    -- false : falls nicht.
   function ist_sub_graph( sub : in graph; der_graph : in graph ) return boolean;

end gerichteter_graph.selektoren.sub_graph;


Das Kind-Paket "gerichteter_graph.selektoren.sub_graph" erweitert das Paket, das die Selektoren enthält, um eine weitere Funktion. Dabei ist es unwesentlich, daß das Selektoren-Paket selber ein Kind-Paket ist. Das Paket "...sub_graph" hat wie sein Vater vollen Zugriff auf die privaten Typen des Wurzelpaketes "gerichteter_graph".

Um zu verhindern, daß durch Kind-Pakete der private Teil des Vater-Pakets offengelegt werden kann, sind folgende Zugriffe nicht erlaubt:
  • Ein Kind-Paket hat in dem öffentlichen Teil seiner Spezifikation keinen Zugriff auf den privaten
    Teil seines Vaters.
  • Ein Kind-Paket hat keinen Zugriff auf den privaten Teil eines seiner Geschwister.
Das gilt nicht für die den privaten Teil der Spezifikationen wie für die Rümpfe. Dort kann auf den privaten Teil der Vater-Spezifikation zugegriffen werden. Der Rumpf eines Vaters kann von einem seiner Kinder abhängen, ebenso wie die Rümpfe von Geschwistern untereinander. Dagegen kann die Spezifikation des Vaters nie von der seiner Kinder abhängen (Übersetzungsreihenfolge).

Ein Kind-Paket benötigt keine "With"-Klausel, damit das Vater-Paket für das Kind-Paket sichtbar ist. Diese Sichtbarkeit ist implizit gegeben.

Ebenso gilt, daß, wenn ein Kind-Paket z. B. einem Unterprogramm bekannt gemacht wird, dieses Unterprogramm damit Zugriff auf die Spezifikation des Vater-Pakets erhält. Wird dem Unter-programm aber das Vater-Paket bekannt gemacht, so hat das Unterprogramm damit keinen Zugriff auf die Spezifikationen von Kind-Paketen.

Die Verwendung von "use" für Kind-Pakete, um z. B. einem Unterprogramm direkten Zugriff auf alle Operatoren zu ermöglichen, impliziert nicht den direkten Zugriff auf die des Vater-Pakets. Das Vater-Paket müßte zusätzlich mit "use" aufgeführt werden.

Die erlaubten bzw. möglichen Zugriffe lassen sich durch die Vorstellung erklären, daß alle Kind-Spezifikationen Teil der Spezifikation ihres Vaters wie auch die Kind-Rümpfe Teile des Vater-Rumpfes sind.

Kind-Pakete können als privat deklariert werden.

Für private Kind-Pakete gilt:
  • Sie sind nur in der Pakethierarchie sichtbar.
  • Sie sind in keiner Spezifikation eines nicht privaten Geschwisterpaketes sichtbar.
    (Im Rumpf eines solchen sind sie allerdings sichtbar.)
  • Im sichtbaren Teil der Spezifikation kann es auf den privaten Teil der Spezifikation seines Vaters
    zugreifen.
Private Kind-Pakete werden für Unterprogramme und Typen eingesetzt, die allen Paketen der Hierarchie bekannt sein sollen (müssen), aber nicht außerhalb der Pakethierarchie sichtbar sein sollen. Damit können private Kind-Pakete auch nicht als einzelne Pakete anderen Programm-einheiten bekannt gemacht werden.


Beispiel:

Abhängig von der Implementierung eines Graphen ("A-priori-Strukturierung", Paket "gerichteter_graph") kann ein privates Paket Unterprogramme zur Verfügung stellen, die auf zusätzlichen Typen operieren. Denn ein Paket "gerichteter_graph" wird auf Typen (z. B. Mengentypen als Container für Ecken und Kanten) aufsetzen, die von anderen Paketen zur Verfügung gestellt werden. Diese sollten, um sie austauschbar zu halten, möglichst geringen Einfluß auf die Implementierung des gerichteten Graphen selber nehmen.

private package gerichteter_graph.interna is
-- ...
end gerichteter_graph.interna;


Das Schlüsselwort "private" verhindert den Zugriff von außen auf die Spezifikation. Alle Geschwister haben dagegen freien Zugriff.


 
zurück
 Index   Ada Tour - Dokumentation  
© 2003 Förderverein Ada Deutschland e.V.