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 Iterable 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 ManItQ 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-Implementierungen 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]
[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