Pakete (Packages)
Mit der zunehmenden Anzahl an Klassen im Programm wird es schwieriger, sich alle Funktionen und Methoden zu merken. Eine sinnvolle Benennung der Klassen und eine sorgfältige Planung, sodass jede Klasse eine klare Verantwortlichkeit hat, sind hilfreich. Zusätzlich ist es sinnvoll, die Klassen in Pakete zu unterteilen. Klassen in einem Paket können Funktionalitäten, Zwecke oder andere logische Eigenschaften teilen.
Pakete sind praktisch Verzeichnisse, in denen die Quellcodedateien organisiert werden.
IDEs bieten bestehende Werkzeuge zur Paketverwaltung. Bisher haben wir nur Klassen und Schnittstellen im Standardpaket des Ordners „Source Packages“ des Projekts erstellt. Sie können ein neues Paket in IntelliJ via File -> New -> Package erstellen. Die einzelnen Aufgaben der wöchentlichen Übung sind in Paketen organisiert.
Sie können Klassen innerhalb eines Pakets auf die gleiche Weise erstellen, wie im Standardpaket. Im Folgenden erstellen wir die Klasse Program
im neu erstellten Paket library
.
Das Paket einer Klasse (das Paket, in dem die Klasse gespeichert ist) wird zu Beginn der Quellcodedatei mit der Anweisung package *name-of-package*;
angegeben. Im folgenden Beispiel befindet sich die Klasse Program
im Paket library
.
package library;
public class Program {
public static void main(String[] args) {
System.out.println("Hello packageworld!");
}
}
Jedes Paket, einschließlich des Standardpakets, kann weitere Pakete enthalten. Zum Beispiel ist im Paket package library.domain
das Paket domain
im Paket library
enthalten. Das Wort domain
wird oft verwendet, um den Speicherplatz für Klassen zu bezeichnen, die Konzepte der Problemstellung repräsentieren. Beispielsweise könnte die Klasse Book
im Paket library.domain
gespeichert sein, da sie ein Konzept der Bibliotheksanwendung darstellt.
package library.domain;
public class Book {
private String name;
public Book(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
Eine Klasse kann auf Klassen in einem Paket zugreifen, indem sie die import
-Anweisung verwendet. Die Klasse Book
im Paket library.domain
wird mit der Anweisung import library.domain.Book;
zur Nutzung verfügbar gemacht. Die Import-Anweisungen, die zum Importieren von Klassen verwendet werden, werden in der Quellcodedatei nach der Paketdefinition platziert.
package library;
import library.domain.Book;
public class Program {
public static void main(String[] args) {
Book book = new Book("the ABCs of packages!");
System.out.println("Hello packageworld: " + book.getName());
}
}
Hello packageworld: the ABCs of packages!
Ab diesem Punkt werden fast alle Übungen Pakete verwenden. Beginnen wir damit, unsere ersten eigenen Pakete zu erstellen.
Verzeichnisstruktur in einem Dateisystem
Jedes Projekt, das Sie in IntelliJ sehen, befindet sich im Dateisystem Ihres Computers oder auf einem zentralen Server.
Das Projektverzeichnis src/main/java
enthält die Quellcodedateien des Programms. Befindet sich das Paket einer Klasse im library
-Paket, wird diese Klasse im src/main/java/libary
-Ordner des Quellcodedateiverzeichnisses gespeichert. Sie können auch die konkrete Projektstruktur in IntelliJ via View -> Windows -> Project überprüfen.
Pakete und Zugriffsmodifikatoren
Bisher haben wir zwei Zugriffsmodifikatoren verwendet. Der Modifikator private
wird verwendet, um Variablen (und Methoden) zu definieren, die nur innerhalb der Klasse sichtbar sind, in der sie definiert sind. Sie können von außerhalb dieser Klasse nicht verwendet werden. Die mit public
definierten Methoden und Variablen hingegen sind für jede und jeden nutzbar.
package library.ui;
public class UserInterface {
private Scanner scanner;
public UserInterface(Scanner scanner) {
this.scanner = scanner;
}
public void start() {
printTitle();
// other functionality
}
private void printTitle() {
System.out.println("***********");
System.out.println("* LIBRARY *");
System.out.println("***********");
}
}
Wenn Sie ein Objekt der Klasse UserInterface
oben erstellen, sind der Konstruktor und die start
-Methode von überall im Programm aufrufbar. Die Methode printTitle
und die Variable scanner
sind jedoch nur innerhalb der Klasse verfügbar.
Fehlt der Zugriffsmodifikator, sind die Methoden und Variablen nur innerhalb desselben Pakets sichtbar. Dies nennen wir den Standard- oder Paketmodifikator. Ändern wir das obige Beispiel so, dass die Methode printTitle
den Paketzugriffsmodifikator hat.
package library.ui;
public class UserInterface {
private Scanner scanner;
public UserInterface(Scanner scanner) {
this.scanner = scanner;
}
public void start() {
printTitle();
// other functionality
}
void printTitle() {
System.out.println("***********");
System.out.println("* LIBRARY *");
System.out.println("***********");
}
}
Nun können die Klassen innerhalb desselben Pakets — also die Klassen im Paket library.ui
— die Methode printTitle
verwenden.
package library.ui;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
UserInterface ui = new UserInterface(scanner
);
ui.printTitle(); // funktioniert!
}
}
Befindet sich eine Klasse in einem anderen Paket, kann die Methode printTitle
nicht aufgerufen werden. Im folgenden Beispiel befindet sich die Klasse Main
im Paket library
. Da die Methode printTitle
im Paket library.ui
ist und den Paketzugriffsmodifikator hat, kann sie nicht verwendet werden.
package library;
import java.util.Scanner;
import library.ui.UserInterface;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
UserInterface ui = new UserInterface(scanner);
ui.printTitle(); // funktioniert nicht!
}
}
Ein größeres Beispiel: Flugkontrolle
Schauen wir uns ein Programm an, das eine Textbenutzungschnittstelle zum Hinzufügen und Überprüfen von Flugzeugen und Flügen bietet. Die Benutzungsschnittstelle des Programms sieht folgendermaßen aus.
Airport Asset Control --------------------
Choose an action: [1] Add an airplane [2] Add a flight [x] Exit Airport Asset Control > 1 Give the airplane id: HA-LOL Give the airplane capacity: 42 Choose an action: [1] Add an airplane [2] Add a flight [x] Exit Airport Asset Control > 1 Give the airplane id: G-OWAC Give the airplane capacity: 101 Choose an action: [1] Add an airplane [2] Add a flight [x] Exit Airport Asset Control > 2 Give the airplane id: HA-LOL Give the departure airport id: HEL Give the target airport id: BAL Choose an action: [1] Add an airplane [2] Add a flight [x] Exit Airport Asset Control > 2 Give the airplane id: G-OWAC Give the departure airport id: JFK Give the target airport id: BAL Choose an action: [1] Add an airplane [2] Add a flight [x] Exit Airport Asset Control > 2 Give the airplane id: HA-LOL Give the departure airport id: BAL Give the target airport id: HEL Choose an action: [1] Add an airplane [2] Add a flight [x] Exit Airport Asset Control > x
Flight Control ------------
Choose an action: [1] Print airplanes [2] Print flights [3] Print airplane details [x] Quit > 1 G-OWAC (101 capacity) HA-LOL (42 capacity) Choose an action: [1] Print airplanes [2] Print flights [3] Print airplane details [x] Quit > 2 HA-LOL (42 passengers) (HEL-BAL) HA-LOL (42 passengers) (BAL-HEL) G-OWAC (101 passengers) (JFK-BAL)
Choose an action: [1] Print airplanes [2] Print flights [3] Print airplane details [x] Quit > 3 Give the airplane id: G-OWAC G-OWAC (101 capacity)
Choose an action: [1] Print airplanes [2] Print flights [3] Print airplane details [x] Quit > x
Es gibt viele "Konzepte", die für diese Problemstellung relevant sind, wesentliche sind durch die Klassen Airplane
und Flight
repräsentiert. Jeder Flug beinhaltet auch einen Place
(Abflugs- und Zielorte). Zusätzlich zu den Konzepten, die die Problemstellung repräsentieren, enthält das Programm auch eine Textbenutzungschnittstelle und eine Klasse, über die die Textbenutzungschnittstelle die Konzepte verwendet.
Die Paketstruktur des Programms könnte folgendermaßen aussehen (zum Beispiel):
-
flightControl
- enthält die Hauptprogramms-Klasse (die Klasse, die bspw.main
enthält), die benötigt wird, um das Programm zu starten. -
flightControl.domain
- enthält die Klassen, die Konzepte der Problemstellung repräsentieren:Airplane
,Flight
undPlace
. -
flightControl.logic
- enthält die Funktionalität, die zur Steuerung der Anwendung verwendet wird. -
flightControl.ui
- enthält die Textbenutzungschnittstelle.
Im nächsten Abschnitt beschreiben wir eine mögliche Organisation des Programms (ohne die Hauptprogramms-Klasse).
Klassen, die Konzepte der Problemstellung repräsentieren
Die Klassen, die Konzepte der Problemstellung repräsentieren, werden oft in einem Paket namens domain
abgelegt. Da sich die gesamte Anwendung im Paket flightControl
befindet, platzieren wir das Paket domain
innerhalb des Pakets flightControl
. Konzepte der Problemstellung werden durch die Klassen Place
, Airplane
und Flight
repräsentiert.
package flightControl.domain;
public class Place {
private String ID;
public Place(String ID) {
this.ID = ID;
}
@Override
public String toString() {
return this.ID;
}
}
package flightControl.domain;
public class Airplane {
private String id;
private int capacity;
public Airplane(String id, int capacity) {
this.id = id;
this.capacity = capacity;
}
public String getID() {
return this.id;
}
public int getCapacity() {
return this.capacity;
}
@Override
public String toString() {
return this.id + " (" + this.capacity + " capacity)";
}
}
package flightControl.domain;
public class Flight {
private Airplane airplane;
private Place departureAirport;
private Place targetAirport;
public Flight(Airplane airplane, Place departureAirport, Place targetAirport) {
this.airplane = airplane;
this.departureAirport = departureAirport;
this.targetAirport = targetAirport;
}
public Airplane getAirplane() {
return this.airplane;
}
public Place getDeparturePlace() {
return this.departureAirport;
}
public Place getTargetPlace() {
return this.targetAirport;
}
@Override
public String toString() {
return this.airplane.toString() + " (" + this.departureAirport + "-" + this.targetAirport + ")";
}
}
Anwendungslogik
Die Anwendungslogik wird in der Regel von den Klassen getrennt, die Konzepte der Problemstellung darstellen. In unserem Beispiel wird die Anwendungslogik im Paket logic
gespeichert. Die Anwendungslogik umfasst die Funktionalität zum Hinzufügen von Flugzeugen und Flügen sowie deren Auflistung.
package flightControl.logic;
import java.util.Collection;
import flightControl.domain.Flight;
import flightControl.domain.Airplane;
import java.util.HashMap;
import java.util.Map;
import flightControl.domain.Place;
public class FlightControl {
private HashMap<String, Airplane> airplanes = new HashMap<>();
private HashMap<String, Flight> flights = new HashMap<>();
private Map<String, Place> places;
public FlightControl() {
this.flights = new HashMap<>();
this.airplanes = new HashMap<>();
this.places = new HashMap<>();
}
public void addAirplane(String ID, int capacity) {
Airplane plane = new Airplane(ID, capacity);
this.airplanes.put(ID, plane);
}
public void addFlight(Airplane plane, String departureID, String destinationID) {
this.places.putIfAbsent(departureID, new Place(departureID));
this.places.putIfAbsent(destinationID, new Place(destinationID));
Flight flight = new Flight(plane, this.places.get(departureID), this.places.get(destinationID));
this.flights.put(flight.toString(), flight);
}
public Collection<Airplane> getAirplanes() {
return this.airplanes.values();
}
public Collection<Flight> getFlights() {
return this.flights.values();
}
public Airplane getAirplane(String ID) {
return this.airplanes.get(ID);
}
}
Textbenutzungschnittstelle
Die Benutzungsschnittstelle ist von der Anwendungslogik und den Klassen, die die Problemstellung repräsentieren, getrennt. In diesem Beispiel wird die Benutzungsschnittstelle im Paket ui
gespeichert.
package flightControl.ui;
import flightControl.domain.Flight;
import flightControl.domain.Airplane;
import java.util.Scanner;
import flightControl.logic.FlightControl;
public class TextUI {
private FlightControl flightControl;
private Scanner scanner;
public TextUI(FlightControl flightControl, Scanner scanner) {
this.flightControl = flightControl;
this.scanner = scanner;
}
public void start() {
// let's start in two parts -- first start the asset control,
// then the flight control
startAssetControl();
System.out.println();
startFlightControl();
System.out.println();
}
private void startAssetControl() {
System.out.println("Airport Asset Control");
System.out.println("--------------------");
System.out.println();
while (true) {
System.out.println("Choose an action:");
System.out.println("[1] Add an airplane");
System.out.println("[2] Add a flight");
System.out.println("[x] Exit Airport Asset Control");
System.out.print("> ");
String answer = scanner.nextLine();
if (answer.equals("1")) {
addAirplane();
} else if (answer.equals("2")) {
addFlight();
} else if (answer.equals("x")) {
break;
}
}
}
private void addAirplane() {
System.out.print("Give the airplane id: ");
String id = scanner.nextLine();
System.out.print("Give the airplane capacity: ");
int capacity = Integer.parseInt(scanner.nextLine());
this.flightControl.addAirplane(id, capacity);
}
private void addFlight() {
System.out.print("Give the airplane id: ");
Airplane airplane = askForAirplane();
System.out.print("Give the departure airport id: ");
String departureID = scanner.nextLine();
System.out.print("Give the target airport id: ");
String destinationID = scanner.nextLine();
this.flightControl.addFlight(airplane, departureID, destinationID);
}
private void startFlightControl() {
System.out.println("Flight Control");
System.out.println("------------");
System.out.println();
while (true) {
System.out.println("Choose an action:");
System.out.println("[1] Print airplanes");
System.out.println("[2] Print flights");
System.out.println("[3] Print airplane details");
System.out.println("[x] Quit");
System.out.print("> ");
String answer = scanner.nextLine();
if (answer.equals("1")) {
printAirplanes();
} else if (answer.equals("2")) {
printFlights();
} else if (answer.equals("3")) {
printAirplaneDetails();
} else if (answer.equals("x")) {
break;
}
}
}
private void printAirplanes() {
for (Airplane plane : flightControl.getAirplanes()) {
System.out.println(plane);
}
}
private void printFlights() {
for (Flight flight : flightControl.getFlights()) {
System.out.println(flight);
System.out.println("");
}
}
private void printAirplaneDetails() {
System.out.print("Give the airplane id: ");
Airplane plane = askForAirplane();
System.out.println(plane);
System.out.println();
}
private Airplane askForAirplane() {
Airplane airplane = null;
while (airplane == null) {
String id = scanner.nextLine();
airplane = flightControl.getAirplane(id);
if (airplane == null) {
System.out.println("No airplane with the id " + id + ".");
}
}
return airplane;
}
}