Klassenstruktur verbessern

Hallo,

ich habe ein Problem bei der Lösung einer meiner Aufgaben in Java. Hoffe ihr könnt mir etwas dabei helfen.

Aufgabenstellung:
Eine ordnungsgemäße Beendigung von Ausgaben in den Ausgabefenstern
wurde durch Implementierung des Interface „DokumentAusgabeIF“ erreicht. Dies erzwang die Implementierung der Methode „ausgabeBeenden“. Dieser Klassenentwurf birgt aber noch eine Unsicherheit, wie sich im Projekt an der später ergänzten Klasse
„MP3DokumentAusgabe“ zeigt: Allzu leicht kann die Implementierung des Interface vergessen werden.

Wie können Sie die Klassenstruktur der Ausgabefenster verbessern, so dass
diese Unsicherheit entfällt? Beschreiben Sie die neue Klassenstruktur (auch
mit Code-Auszügen, ggf. in einem Klassendiagramm) und erläutern Sie, welche
Anpassungen Sie im Projekt vornehmen müssen.

Um eine bessere Übersicht von allen Klassen zu erhalten, habe ich diese mit UML dargestellt.
http://www.image-hoster.de/files/72155fa89ce5144ee20a72b32.png (da das Bild recht groß ist, habe ich es nur als Link eingefügt!)

Was mir aber auch nicht viel geholfen hat. Vielleicht verstehe ich auch die Frage nur falsch.

Ich habe schon daran gedacht, eine neue Klasse “DokumentAusgabe” zu erstellen, die dann dass Interface „DokumentAusgabeIF“ implementiert. Und die Klassen “TonDokumentAusgabe”, “MP3DokumentAusgabe” und “BildDokumentAusgabe” als Subklasse von “DokumentAusgabe” (… extends DokumentAusgabe) zu machen. Aber das wäre doch genauso einfach zu vergessen wie die Implementierung des Interface, oder nicht?

Bin für jede Anregung dankbar.

Gruß Tosso

“Eine ordnungsgemäße Beendigung von Ausgaben in den Ausgabefenstern
wurde durch Implementierung des Interface „DokumentAusgabeIF“ erreicht.”

bezieht sich das auf eine bestimmte der vorhandenen Klassen?

“wie sich im Projekt an der später ergänzten Klasse
„MP3DokumentAusgabe“ zeigt: Allzu leicht kann die Implementierung des Interface vergessen werden”

die Ausgabe-Klassen werden nirgendwo sonst im Klassendiagramm referenziert,
ist das alles theoretisch oder ist die Nutzung im Programm klar?
wenn man das Interface, die Methode vergisst, was passiert dann?

wenn die Klasse einfach nur herumsteht ist es auch egal ob sie die Methode hat oder nicht,
ruft irgendjemand die Methode auf? dann sollte es eigentlich auch schon auffallen…

unschön kommt hinzu, dass andere Klassen als die Ausgabe-Klassen, nämlich die Document-Klassen, bereits documentAusgeben()-Methoden haben,
ist da ein Zusammenhang erkennbar, sollen diese Klassen jeweils ausgabeBeenden() aufrufen?

ein Weg wäre vielleicht, in der Oberklasse Document eine fertige Methode zum Ausgabe-Ablauf abzulegen,
in welcher zwei zu überschreibende Methoden aufgerufen werden,
documentAusgeben() und ausgabeBeenden(),
dann müsste man sich in jeder Subklasse um beide Methoden kümmern und denkt hoffentlich dran,

das ganze Interface DocumentAusgabeIF + deren Klassen sind dann aber evtl. gar nicht mehr nötig…

[QUOTE=SlaterB]“Eine ordnungsgemäße Beendigung von Ausgaben in den Ausgabefenstern
wurde durch Implementierung des Interface „DokumentAusgabeIF“ erreicht.”

bezieht sich das auf eine bestimmte der vorhandenen Klassen?[/QUOTE]
Das bezieht sich auf die Klassen “TonDokumentAusgabe”, “MP3DokumentAusgabe” und “BildDokumentAusgabe”. Diese Klassen erstellen ein neues Fenster um das jeweilige Dokument wiederzugeben.

[QUOTE=SlaterB;21616]“wie sich im Projekt an der später ergänzten Klasse
„MP3DokumentAusgabe“ zeigt: Allzu leicht kann die Implementierung des Interface vergessen werden”

die Ausgabe-Klassen werden nirgendwo sonst im Klassendiagramm referenziert,
ist das alles theoretisch oder ist die Nutzung im Programm klar?
wenn man das Interface, die Methode vergisst, was passiert dann?[/QUOTE]
Das Programm arbeitet fehlerfrei. Wenn die Methode “ausgabeBeenden()” weggelassen wird, wird die Ausgabe nicht richtig beendet, z. B. spielt dann die Musik trotz schließen des Fensters weiter.

[QUOTE=SlaterB;21616]unschön kommt hinzu, dass andere Klassen als die Ausgabe-Klassen, nämlich die Document-Klassen, bereits documentAusgeben()-Methoden haben,
ist da ein Zusammenhang erkennbar, sollen diese Klassen jeweils ausgabeBeenden() aufrufen?[/QUOTE]
Die Dokument-Klassen rufen lediglich die jeweilige DokumentAusgabe-Klasse auf.

Ich poste mal den kompletten Code der MP3-Klassen.

	private static final long serialVersionUID = 1L;
	public MP3Dokument(String fileName, String filePath, String beschreibung) {
		super(fileName, filePath, beschreibung);
	}

	@Override
	public void dokumentAusgeben() {
		new MP3DokumentAusgabe(this);
	}
}```

import java.awt.Color;
import java.awt.Graphics;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javazoom.jl.decoder.JavaLayerException;
import javazoom.jl.player.Player;

public class MP3DokumentAusgabe extends JFrame implements DokumentAusgabeIF {
private static final long serialVersionUID = 1L;
private File mp3File;
private Player jlPlayer;

public MP3DokumentAusgabe(MP3Dokument mp3Dokument) {
	mp3File = new File(mp3Dokument.getFilePath());
	this.setTitle("Ausgabe MP3-Dokument");
	this.setSize(300, 100);
	this.setLocation(Konstanten.positionX(), Konstanten.positionY());
	this.addWindowListener(new DokumentAusgabeEventHandler());
	this.setContentPane(new JPanel() {
		@Override
		public void paintComponent(Graphics g) {
			g.setColor(Color.BLUE);
			g.drawString("Es erklingt \"" + mp3File.getName() + "\"", 20,
					35);
		}
	});
	this.setVisible(true);
	// jetzt den Thread erzeugen und starten:
	Thread playerThread = new Mp3Player();
	// veranlasst die Ausführung von "run":
	playerThread.start();
}

/*
 * Sicherstellung der Multidokumentenausgabe mit (innerer) Thread-Klasse zum
 * Ausführen des mp3-Players als Thread, da sonst die Anwendung blockiert
 * wird, solange die Player#play-Methode arbeitet.
 */
class Mp3Player extends Thread {
	@Override
	public void run() {
		this.playMP3();
	}

	public void playMP3() {
		try {
			FileInputStream inputStream = new FileInputStream(mp3File);
			jlPlayer = new Player(inputStream);
			jlPlayer.play();
			// weiter erst nach Beendigung von "play":
		} catch (FileNotFoundException fnfe) {
			System.out.println(fnfe.toString());
		} catch (JavaLayerException jle) {
			System.out.println(jle.toString());
		}
	}

}

public void ausgabeBeenden() {
	jlPlayer.close();
	this.dispose();
}

}```

also mit

hattest du ja eh schon denselben Gedanken,
meiner Ansicht nach ist der Trick gezielte Programmsteuerung, so dass Vergessen nur in den gröberen Varianten vorkommen kann,

‚extends DokumentAusgabe‘ kann man nicht vergessen falls eine zentrale Methode X genau dies als Parameter verlangt,
dazu ist also noch weitergehend nötig zu wissen, wo alles aufgerufen wird,

Dokument selber ist etwa interessant:

    @Override
    public void dokumentAusgeben() {
        new MP3DokumentAusgabe(this);
    }

diese Methode enthält nichts spannendes, die könnte auch in die Basisklasse

    public final void dokumentAusgeben() {
        erstelleAusgabe().play(this);
    }

    public abstract DokumentAusgabe erstelleAusgabe();

Subklassen geben dann z.B. MP3DokumentAusgabe,
durch den Datentyp DokumentAusgabe gäbe es einen Fehler, wenn MP3DokumentAusgabe nicht von DokumentAusgabe erbt,

auf Basisklasse vielleicht doch lieber verzichten,
wenn die Aufgabe vom Interface spricht dann sollte man das nicht quasi überflüssig machen,
DokumentAusgabeIF an allem vorherigen eingesetzt sorgt genauso für Sicherheit, anderseits gibts da keine play-Methode…

wenn viele Dinge in den Ausgabeklassen gleich sind, etwa das Erben von JFrame oder das Starten eines Threads, dann ist eine Basisklasse für die Ausgabeklassen wiederum sowieso günstig

Eine zentrale Methode X habe ich jetzt zwar nicht, aber in einer späteren Aufgabe sollen alle Ausgabefenster von oben links nach unten rechts angeordnet werden. Die Methoden selber sollen aber nur einmal geschrieben werden, was ja auch Sinn ergibt. Dafür habe ich zwei Methoden “positionX()” und “positionY()” geschrieben, die ich jetzt in der neuen Klasse
“DokumentAusgabe” aufgenommen habe. Vorher hatte ich sie noch in der Klasse “Konstanten” untergebracht, wo sie ja eigentlich nicht hingehört haben.

Die neue Klasse DokumentAusgabe:


public abstract class DokumentAusgabe extends JFrame implements DokumentAusgabeIF {
	private static final long serialVersionUID = 1L;
	
	private static int posX = Konstanten.posX;
	private static int posY = Konstanten.posY;
	
	public abstract void ausgabeBeenden();
	
	public static int positionX() {
		posX = posX + 30;
		return posX;
	}
	
	public static int positionY() {
		posY = posY + 30;
		return posY;
	}
}```

und Anpassung der Ausgabeklassen:
```public class MP3DokumentAusgabe extends DokumentAusgabe {
...
this.setLocation(super.positionX(), super.positionY());
...
}```

Dadurch spare ich zumindest den "import javax.swing.JFrame" in den einzelnen Ausgabeklassen. Und davon ausgehend, dass jede Ausgabeklasse die DokumentAusgabe als Erweiterung haben muss, müsste es doch sicherer sein. Wobei mir jetzt das Interface als überflüssig erscheint. Aber ein Versuch vom Fernlehrer einen Hinweis zu bekommen, ob ich das Interface durch meine DokumentAusgabe-Klasse ersetzen darf, wurde mir wie folgt beantwortet:

"...da bereits in der Aufgabe stand, dass die Klassenstruktur so verbessert werden soll, so dass diese Unsicherheit entfällt, erübrigt sich Ihre Frage. Ist diese Antwort kryptisch genug, wenn sie nicht die Lösung enthalten soll?"

Ehrlich gesagt, hat mir die Antwort vom Fernlehrer kein bisschen geholfen...

für mich klingt es nach einer Bestätigung, dass das ein besserer Ansatz ist,
aber das wird mehr und mehr psychologisch,
meine Idee ist anscheinend angekommen, die Auswahl und Details obliegen dir

Dann erstmal vielen Dank für deine Hilfe :slight_smile: