IO/UI

Aus Programmieren-Wiki
🚧 Diese Seite befindet sich in Bearbeitung 🚧
🤓 Diese Seite ist eine Bewertungsrichtlinie, die ab Blatt 2 annotiert und ab Blatt 3 abgezogen wird. 🤓

Beschreibung

Einleitung

Wir benutzen zu Debugging-Zwecken gerne System.out.println innerhalb von Codestellen. Diese Methode ist zwar ein einfacher Weg, schnell Code zu debuggen, allerdings ist das als eine finale Benutzerschnittstelle nicht geeignet. Generell gilt es Benutzerinteraktion nicht auf das Programm zu verteilen, sondern eine geeignete, zentralisierte Benutzerschnittstelle zu erstellen. Tut man das nicht, ergeben sich folgende gravierende Nachteile:

  • Schlechte Wartbarkeit: Es müssen an vielen Stellen Änderungen gemacht werden. Durch die Mischung werden diese Änderungen sehr verschwert
  • Schlechte Flexibilität: Schnelles Ändern der Benutzerschnittstelle oder der Programmlogik können Seiteneffekte verursachen
  • Ablauf schwer zu verstehen: Ist bspw. der Fehler in der Schnittstelle, der Logik oder wurde eine falsche Eingabe getätigt?
  • Schlechte Testbarkeit: Es lässt sich schwer zwischen "debugging"- und "normalen-" Ausgaben unterscheiden bzw. erzeugt eine gute Trennung viel Aufwand

Definition

Wir bezeichnen die Trennung innerhalb des Quellcodes zwischen Teilen, die für die Interaktion gedacht sind, und Teilen, die für das eigentliche Programm bestimmt sind, als Trennung der Benutzerinteraktion und Programmlogik. Im Falle von Java (und demnach des Übungsbetriebes) bedeutet das:

  • Klassen für Programmlogik:
    • Erstellen keine Ausgaben
    • Lesen keine Eingaben
    • Validiert Semantik der Eingaben, d.h. Prüfung der eingegebenen Werte bezogen auf die Implementierung der jeweiligen Klasse
    • Besitzen öffentliche Schnittstelle von Methoden durch die von "außen" der Programmablauf gesteuert werden kann
  • Klassen für Benutzerinteraktion:
    • Liest Eingaben
    • Erstellt Ausgaben
    • Benutzt die öffentliche Schnittstelle um mit dem Programm zu kommunizieren
    • Validiert Syntax der Eingaben (z.B. Ist die Zahl ein Integer?), d.h. die Sinnhaftigkeit und Korrektheit der Werte

Umsetzung

Zunächst wird gefordert, selbst IO und Programmlogik zu trennen. In späteren Blättern gibt es allerdings eine Vorlage für ein Command Pattern Hier eine Anleitung, dieses Prinzip leicht selbst umzusetzen:

  1. public static void main(String[] args) kommt in eine eigene Klasse. Hier wird nur Benutzerinteraktion getätigt.
  2. Identifizieren der "obersten" Klasse. Bspw. bei einer Implementierung eines Brettspiels die Klasse für das Spielbrett, welche sich selbst mit Spielfiguren initialisiert.
  3. Die Klasse benötigt eine ausreichende Schnittstelle für die Benutzerinteraktionsklasse (getter- und setter-Methoden zum Beispiel).
  4. Alle Klassen, die nicht an der Benutzerinteraktion beteiligt sind, dürfen keine Ein-/Ausgaben tätigen.
  5. Die Semantik wird jetzt von der Programmlogikklasse geprüft. Hier darf das Programm auch abstürzen, es ist die Aufgabe des Anwenders diese Werte vorher zu prüfen! Bspw. wird für die Erstellung eines Arrays ein positiver Wert benötigt.
  6. Die Syntax wird von der Benutzerinteraktionsklasse geprüft, bevor sie versuchen, diese an die Programmlogikklassen zu übergeben. Zusätzlich zu den Einschränkungen aus 5, müssen die Benutzerinteraktionsklassen auch prüfen, ob die Eingaben dem geforderten Anwendungsprofil entsprechen. Als Beispiel ist eine Zeichenkette als Geschwindigkeit eines Autos unzulässig.


Negativbeispiel

Für sauberen und vor allem wiederverwendbaren Programmcode ist also eine klare Trennung von Programmlogik und Benutzerinteraktion wichtig. Die Logik oder das Modell sollten in verschiedenen Kontexten wiederverwendbar sein und nicht direkt an eine Art der Benutzerinteraktion gebunden sein. Durch Terminalausgaben in einer „Modellklasse“ wird diese Klasse an eine bestimmte Methode der Ausgabe (das Terminal) gebunden, sodass es schwierig bis unmöglich ist, sie im Rahmen einer Graphischen Nutzerschnittstelle (GUI) zu verwenden. Im Beispiel könnte man die Klasse Account nicht im Rahmen einer GUI verwenden, da es keine Möglichkeit gibt, den Kontostand vom Account abzufragen, da dieser nur über das Terminal ausgegeben wird. Auf der anderen Seite sollte das UI keine Programmlogik enthalten und so lose wie möglich an die Logik gekoppelt sein.

public class UI {
	public static void main(String[] args) {
		Account a = new Account(100);
		while(true) {
			System.out.println("Input an Integer");
			String input = ""; 
			// We will imagine the input being produced here
			int amount = 0;
			try {
				amount = Integer.parseInt(input);
			} catch(NumberFormatException e) {
				continue;
			}
			a.transfer(amount);
		}
	}
}

public class Account {
	private int balance;
	
	public Account(int startingBalance) {
		balance = startingBalance;
	}
	
	public void transfer(int amount) {
		if (balance + amount > 0) {
			balance += amount;
			System.out.println("Balance: " + balance);
		} else {
			System.out.println("Unable to make transfer. Your balance is: " + balance);
		}
	}
}

Positivbeispiel

public class UI {
	public static void main(String[] args) {
		while(true) {
			System.out.println("Input an Integer");
			String input = "";
			// We again imagine the input is present now
			int amount = 0;
			try {
				amount = Integer.parseInt(amount);
			} catch(NumberFormatException e) {
				continue;
			}
		}
		
		if (a.transfer(amount)) {
			System.out.println("Balance: " + a.getBalance());
		} else {
			System.out.println("Unable to make transfer. Your balance is: " + a.getBalance())
		}
	}
}

public class Account {
	private int balance;
	
	public Account(int startingBalance) {
		this.balance = startingBalance;
	}
	
	public boolean transfer(int amount) {
		if (balance + amount > 0) {
			balance += amount;
			return true;
		} else {
			return false;
		}
	}
	
	public int getBalance() {
		return balance;
	}
}


Wenn du diese Seite interessant fandest, findest du hier noch mehr Seite(n) dazu:
Komplexität