homedukeOOP mit Java: Dynamische Datenstrukturen und Containerklassen Prof. Dr. Uwe Schmidt FH Wedel

Dynamische Datenstrukturen und Containerklassen

weiter

weiter

Containerklassen

Konzepte
es sind nur wenige konzeptionell unterschiedliche ADTs notwendig
 
Listen, Mengen, Verzeichnisse, Multimengen, Relationen, Graphen
weiter
Implementierungen
es gibt viele sehr ausgefeilte Algorithmen und Datenstrukturen zur effizienten Implementierung dieser Konzepte
 
verkettete Listen, Bäume, Felder, hash-Tabellen, ...
 
--> Literatur über Algorithmen und Datenstrukturen
weiter
merke
wenige gemeinsame Schnittstellen für alle Implementierungen:
Verständlichkeit
weiter
Umsetzung
in Hierarchien von Klassen-- und Schnittstellenvererbungen
weiter
1.Schritt
Gemeinsamkeiten für alle Container festlegen
weiter
Beispiel
public
interface ContainerPredicates<E> {
 
  public
  boolean isEmpty();
 
  public
  boolean isIn(E e);
 
  public
  int noOfElements();
 
  // ...
}
weiter
merke
Allgemein nützliche Schnittstellen definieren
--> Beispiel: totale Ordnung
weiter
2.Schritt
vollständige Schnittstelle für ein Container-Konzept festlegen
weiter
Beispiel
für Listen
 
public
interface List<E>
  extends ContainerPredicates<E>
          // , OtherInterfaces<E>
{
  public
  E hd();
 
  public
  List<E> tl();
 
  public
  E get(int i);
 
  public
  List<E> cons(E e);
 
  public
  List<E> append(E e);
 
  // ...
}         
weiter
Beispiel
für Mengen
 
public
interface Set<E>
  extends ContainerPredicates<E>
          // , OtherInterfaces<E>
{
  public
  void insert(E e);
 
  public
  void remove(E e);
 
  // ...
}         
weiter
3.Schritt
Allgemeingültige Eigenschaften eines Container-Konzepts in einer abstrakten Klasse implementieren
weiter
Beispiel
für Listen
 
public
abstract
class ListImpl<E>
  implements List<E>
{
  public
  boolean isEmpty() {
    return
      noOfElements() == 0;
  }
 
  public
  E hd() {
    return
      get(0);
  }
 
  // ...
}
weiter
4.Schritt
vollständige konkrete Implementierungen entwickeln.
weiter
Beispiel
unter Verwendung einer Klasse für verkettete Listen.
--> LinkedList
 
Benutzung der verketteten Liste erlaubt das Beerben der abstrakten Klasse
 
public
class ListAsLinkedList<E>
  extends ListImpl<E>
{
  protected
  LinkedList l;
 
  //--------------------
 
  public
  ListAsLinkedList() {
    l = LinkedList.mkEmptyList();
  }
 
  //--------------------
  // isEmpty() und hd()
  // sind schon implementiert
 
  // eine Redefinition kann
  // zur Effizienzsteigerung noetig werden
  // hier z.B. fuer isEmpty()
 
  public
  int noOfElements() {
    return
      l.len();
  }
 
  public
  boolean isIn(E e) {
    return
      l.isIn(e);
  }
 
  public
  List<E> append(E e) {
    l.append(e);
    return
      this;
  }
 
  public
  E get(int i) {
    return
        (E)(l.at(i));
  }
 
  // ...
}
weiter
2. Beispiel
mit der Klasse Vector aus dem JDK
 
import java.util.Vector;
 
public
class ListAsVector<E>
  extends ListImpl<E>
{
  protected
  Vector<E> v;
 
  //--------------------
 
  public
  ListAsVector() {
    v = new Vector<E>();
  }
 
  //--------------------
  // isEmpty() und hd()
  // sind schon implementiert
 
  // eine Redefinition kann
  // zur Effizienzsteigerung noetig werden
  // hier z.B. fuer isEmpty()
 
  public
  int noOfElements() {
    return
      v.size();
  }
 
  public
  boolean isIn(E e) {
    return
      v.contains(e);
  }
 
  public
  List<E> append(E e) {
    v.addElement(e);
    return
      this;
  }
 
  public
  E get(int i) {
    return
      v.elementAt(i);
  }
 
  // ...
}
 
weiter
merke
In allen Anwendungen können für Listen ausschließlich Variablen vom Typ List verwendet werden, d.h. Variablen von der abstrakten Klasse, die das Konzept festlegt.
weiter
merke
Alle Listen können gleichartig verarbeitet werden.
 
Eigene Routinen, die mit Listen arbeiten, können jede Art von Listen, auch gemischt verarbeiten.
weiter
merke
Nur die Konstruktoren brauchen explizit die konkreten Klassen
weiter
merke
Konstruktoren nicht über eine ganze Anwendung verstreuen, sondern in sogenannten Fabrikmethoden bündeln
--> Entwurfsmuster
weiter

weiter

Verarbeitung aller Elemente eines Containers

Methoden
sind nicht als Parameter erlaubt.
forall Routinen mit Funktionen als Parameter zur Verarbeitung aller Elemente sind damit nicht möglich.
weiter
Ausweg
eine abstrakte Klasse mit einer Methode als Kommando definieren
 
diese Klasse beerben und mit eigenen Methoden konkretisieren
weiter
dynamisches Binden
durch Vererbung und dynamisches Binden werden Methoden als Parameter simuliert
weiter
Beispiel
Command
eine abstrakte Kommando-Klasse
 
abstract
public
class Command {
  abstract
  public
  void process(Object o);
}
weiter
PrintCommand
eine konkrete Klasse für eine Ausgabe
 
public
class PrintCommand 
  extends Command
{
  public
  void process(Object o) {
    System.out.println(o.toString());
  }
}
weiter
Anwendung
eine einfache Array-Klasse mit einer Methode zur Verarbeitung aller Elemente
Array
public
class Array {
  Object [] a;
 
  // ... vieles mehr
 
  //--------------------
 
  public
  void forall(Command c) {
    for (int i = 0;
         i < a.length;
         ++i ) {
      c.process(a[i]);
    }
  }
 
  //--------------------
 
  public
  void print() {
    forall(new PrintCommand());
  }
 
}
weiter
merke
die print-Methode verwendet die forall-Kontrollstruktur wieder.
weiter
merke
die print-Methode ist unabhängig von der konkreten Ausprägung der forall-Kontrollstruktur
weiter
merke
die print-Methode kann in eine abstrakte Oberklasse von Array ausgelagert werden und von vielen Klassen genutzt werden.
weiter
Beispiel
Container
eine abstrakte Oberklasse mit implementierter print-Methode
 
abstract
public
class Container {
 
  //--------------------
 
  abstract
  public
  void forall(Command c);
 
  //--------------------
 
  public
  void print() {
    forall(new PrintCommand());
  }
 
}
weiter
Array1
eine abgeleitete Klasse, die nur die forall-Kontrollstruktur definiert
 
public
class Array1
  extends Container
{
  Object [] a;
 
  // ... vieles mehr
 
  //--------------------
 
  public
  void forall(Command c) {
    for (int i = 0;
         i < a.length;
         ++i ) {
      c.process(a[i]);
    }
  }
 
  //--------------------
  // print ist schon vorhanden
}
weiter
merke
viele Methoden können so in gemeinsamen Oberklassen nur einmal implementiert und für die unterschiedlichsten Datenstrukturen wiederverwendet werden
weiter
merke
für jedes Kommando, auch für Kommandos, die nur einmal verwendet werden, muss eine neue Kommando-Klasse erstellt werden
weiter
merke
Namensraum der Klassen wird unnötig überfüllt.
 
weiter
merke
Kommandos können einen lokalen Zustand besitzen, dieser wird durch einen Konstruktor mit Parametern initialisiert
weiter
Beispiel
import java.io.PrintStream;
 
public
class PrintStreamCommand 
  extends Command
{
  PrintStream ps;
 
  public
  PrintStreamCommand() {
    this.ps = System.out;
  }
 
  public
  PrintStreamCommand(PrintStream ps) {
    this.ps = ps;
  }
 
  public
  void process(Object o) {
    ps.println(o.toString());
  }
}
 
weiter
Anwendung
import java.io.PrintStream;
 
abstract
public
class Container1
  extends Container
{
 
  //--------------------
 
  public
  void print(PrintStream ps) {
    forall(new PrintStreamCommand(ps));
  }
 
}
weiter
2. Beispiel
Akkumulieren eines Resultats
 
Beispiel:
Berechnung der Summe aller Elemente
 
public
class SumCommand
  extends Command
{
  int sum;
 
  public
  SumCommand() {
    sum = 0;
  }
 
  public
  void process(Object o) {
    sum += ((Integer)o).intValue();
  }
 
  public
  int getSum() {
    return
      sum;
  }
}
weiter
Anwendung
abstract
public
class Container2
  extends Container1
{
 
  //--------------------
 
  public
  int sum() {
    SumCommand c = new SumCommand();
 
    forall(c);
 
    return
      c.getSum();
  }
 
}
weiter
merke
die Technik, Methoden in Objekten zu speichern, und diese möglicherweise auch erst später aufzurufen, wird intensiv für die Ereignisbehandlung eingesetzt.
weiter
Beispiel
weiter
Beispiel
weiter

Letzte Änderung: 14.02.2012
© Prof. Dr. Uwe Schmidt
Prof. Dr. Uwe Schmidt FH Wedel