19. Persistenz Inhaltsverzeichnis 21. Klassendiagramme

20. Funktionale Abstraktion

Wenn Persistenz mit unterschiedlichen Techniken (Textstrom/Binärstrom) für unterschiedliche Queue-Implementierungen (Reihung/verkettete Liste) programmiert wird, dann fällt die Gemeinsamkeit im Steuerungsfluss aller Implementierungen (auch die von equals und copy) auf: Alle Elemente werden in einer Wiederholung (Schleife) durchlaufen und mit jedem Element eine bestimmte Operation (speichern, kopieren, vergleichen, usw.) durchgeführt. Dies führt zum Konzept der Iteratoren: Die Implementierung der Schnittstelle Ite­rable vereinfacht alle diese Methodenrümpfe.

Um die Vorteile eines Iterators zu demonstrieren, definieren wir eine manipulierbare Warteschlange: ManQ ist eine Erweiterung von Queue um die Methode manipulate, die alle gespeicherten Elemente mit einer beliebigen Methode manipulieren (verändern) kann. Hierzu muss ihr ein Objekt vom Typ

trait Manipulator[E] {

   def manipulator(e: E) }

als Parameter übergeben werden. Die Methode manipulator führt die gewünschten Änderungen an jedem Element aus. Beispielsweise können allen StringBuffer-Objekten in einer Warteschlange ein '€' angehängt werden. Die Schnittstelle ManQ muss (hier anonym) implementiert, d.h. die Methode manipulate programmiert werden:

def euroAnhängen (q: ManQ[StringBuffer]) =

   q.manipulate(new Manipulator[StringBuffer] {

      def manipulator(s: StringBuffer) = {

        s.append("€") } } )

Um die Methode manipulate sehr einfach implementieren zu können, erweitern wir die Queue-Implementierungen um einen Iterator:

/** Iterator for ArrayQ - ItArrayQ.scala */

trait ItArrayQ[E] extends ArrayQ[E] with collection.Iterable[E] {

   override def isEmpty = super.isEmpty // [1]

   def iterator =

     new Iterator[E] {

        private var i = 0

        def hasNext = {

           i < anzahl }

        def next = {

           val nächstes = inhalt((ältestes + i) % inhalt.length)

           i = i + 1

           nächstes } } }

/** Iterator for ListQ */

trait ItListQ[E] extends ListQ[E] with collection.Iterable[E] {

   override def isEmpty = super.isEmpty

   def iterator =

     new Iterator[E] {

        private var n = ältester

        def hasNext = {

           n != None }

        def next = {

           val nächster = n.get.wert

           n = n.get.verbindung

           nächster.get.asInstanceOf[E] } } }

Ein Objektdiagramm stellt die nicht sofort einleuchtende Struktur eines Iterators verständlich dar:

Abbildung 41: Iterator für ArrayQ[2]

In Anwesenheit des Iterators kann nun die Methode manipulate sehr einfach implementiert werden:

/** Manipulable Queue for Iterable - ManQ.scala */

trait ManItQ[E] extends ManQ[E] with collection.Iterable[E] {

   def manipulate(m: Manipulator[E]) = {

     val i = iterator

     while (i.hasNext) {

        m.manipulator(i.next) } } }

Ein besonderer Vorteil des Iterators ist, dass die Methode manipulate gemeinsam für ItArrayQ und ItListQ ist. So kann das obige Trait einfach ins ItArrayQ und ItListQ hineingemischt werden:

class ManListQ[E] extends ItListQ[E] with ManItQ[E]

class ManArrayQ[E](size: Int) extends ArrayQ[E](size) // [3]

   with ItArrayQ[E] with ManItQ[E]

Hier wird die Überlegenheit von Scala’s trait über Java’s interface deutlich: Wegen fehlender Mehrfachbeerbung können in Java nicht gleichzeitig ArrayQ und Man­ItQ erweitert werden.

Ähnlich elegant können sich die folgenden Neuimplementierungen von equals[4], copy und save (diesmal in eine Textdatei; damit sie mit load wiederhergestellt werden kann, muss die Funktion fromString in der Klasse des aktuellen Typparameters überschrieben werden[5]) des Iterators bedienen:

/** ExtQ for Iterable - ExtItQ.scala */

trait ExtItQ[E] extends ExtQ[E] with Iterable[E] {

   def equals(that: ExtQ[E]) = {

     val dieser = this.iterator

     val jener = that.asInstanceOf[ExtItQ[E]].iterator

     while (dieser.hasNext && jener.hasNext)

        if (dieser.next != jener.next)

           false

     !dieser.hasNext && !jener.hasNext }

   def copy(q: ExtQ[E]) = {

     this.clear

      val that = q.asInstanceOf[ExtItQ[E]]

     val jener = that.iterator

     while (jener.hasNext)

        this.add(jener.next) } }

Dieses Trait kann nun ohne weitere Programmierung in die Queue-Implemen­tie­run­gen eingemischt werden:

class ExtItListQ[E] extends ItListQ[E] with ExtItQ[E]

class ExtItArrayQ[E](size: Int) extends ArrayQ[E](size) // [6]

   with ItArrayQ[E] with ExtItQ[E]

19. Persistenz Inhaltsverzeichnis 21. Klassendiagramme

[1] ist für Eindeutigkeit nötig, weil scala.Iterable auch isEmpty enthält

[2] ähnlich für ListQueue

[3] Hier muss ArrayQueue genannt werden, um ihm den Klassenparameter übergeben zu können.

[4] Diese Version von equals vergleicht den Inhalt von Warteschlangen auch unterschiedlicher Typen, sofern sie nur Iterable implementieren – hierdurch kann jedoch Symmetrie verloren gehen, was für equals() typischerweise gefordert wird: a.equals(b) == b.equals(a).

[5] eine alternative Vorgehensweise haben wir in PersArrayQ (mit Reflexion aus XML) programmiert

[6] Hier muss ArrayQ extra genannt werden, um ihm Klassenparameter übergeben zu können. ListQ (ohne Klassenparameter) kann von ItListQ geerbt werden.


Version: 5. Dezember 2010

© Prof. Solymosi, 2010, Beuth-Hochschule für Technik Berlin