Projekt ma na celu stworzenie systemu bibliotecznego, który umożliwia wypożyczanie różnych rodzajów przedmiotów, takich jak książki, magazyny, filmy i muzyka. System powinien być elastyczny i rozszerzalny, co pozwoli na łatwe dodawanie nowych rodzajów przedmiotów do wypożyczenia w przyszłości.
W projekcie wykorzystane zostaną interfejsy i klasy abstrakcyjne, aby zapewnić elastyczność i rozszerzalność systemu. Interfejsy będą definiować metody, które muszą być zaimplementowane przez klasy reprezentujące różne rodzaje przedmiotów, a klasy abstrakcyjne będą zawierać wspólne implementacje dla tych klas.
Użyte zostaną również proste struktury danych, takie jak listy i mapy, do przechowywania informacji o przedmiotach i ich dostępności. System będzie umożliwiał dodawanie nowych przedmiotów do biblioteki, sprawdzanie dostępności przedmiotów oraz wypożyczanie i zwracanie przedmiotów przez użytkowników.
- Interfejsy są umowami, które definiują zestaw metod, które klasa musi implementować. Można powiedzieć, że interfejsy są jak kontrakty, które klasy muszą podpisać.
- Abstrakcyjne klasy to klasy, które nie mogą być instancjonowane, ale mogą zawierać implementacje metod, które mogą być dziedziczone przez klasy potomne. Abstrakcyjne klasy mogą również definiować metody abstrakcyjne, które muszą być zaimplementowane przez klasy potomne.
- Klasy to konkretne implementacje, które mogą być instancjonowane i które implementują interfejsy lub dziedziczą po klasach abstrakcyjnych.
- Interfejsy to umowy, które definiują zestaw metod, które klasa musi implementować, podczas gdy klasy abstrakcyjne to klasy, które mogą zawierać pola i metody, które mogą być dziedziczone przez klasy potomne.
- Interfejsy nie mogą zawierać pól, podczas gdy klasy abstrakcyjne już mogą.
- Interfejsy mogą być implementowane przez wiele klas, podczas gdy klasy abstrakcyjne mogą być dziedziczone tylko przez jedną klasę potomną.
- Pamiętaj o przerwach co 30-40 minut, aby uniknąć zmęczenia i poprawić swoją koncentrację. Przejdź się, rozciągnij, zrób sobie herbatę lub kawkę :D
- Rób backupy swojej pracy, aby móc cofnąć do poprzedniej wersji, jeśli coś pójdzie nie tak.
- Najlepiej użyj systemu kontroli wersji, takiego jak Git, aby śledzić zmiany w kodzie i móc łatwo wrócić do poprzednich wersji. Jeśli nie potrafisz z niego korzystać, to nie martw się, wystarczy, że będziesz robić kopie zapasowe
- Zachowuj porządek w kodzie, używaj odpowiednich nazw klas, metod i zmiennych, aby kod był czytelny i zrozumiały.
Klasa Person będzie reprezentować dowolną osobę, może to być użytkownik biblioteki lub pracownik, albo nawet autor książki. Klasa ta będzie zawierać podstawowe informacje o osobie, takie jak imię, nazwisko.
-
Stwórz katalog
modelwewnątrz kataloguproject.W tym katalogu będziemy przechowywać wszystkie klasy modelujące nasze obiekty, takie jak
Person,Book,Magazine, itp. -
Wewnątrz katalogu
modelstwórz abstrakcyjną klasęPerson. -
Wewnątrz klasy utwórz prywatne pola
firstNameilastNamez typemString. -
Dodaj publiczną metodę
getFirstName(), która zwraca imię osoby. -
Dodaj publiczną metodę
getLastName(), która zwraca nazwisko osoby. -
Dodaj publiczną metodę
getFullName(), która zwraca pełne imię i nazwisko osoby, łącząc imię i nazwisko z użyciem spacji.- Jeśli imię lub nazwisko są
null, metoda powinna zwracać tylko dostępne pole.
- Jeśli imię lub nazwisko są
-
Dodaj prywatną metodę
formatName(String name), która będzie formatować imię lub nazwisko, aby zawsze zaczynało się wielką literą.-
W tym celu użyj metody
substring(0, 1).toUpperCase() + substring(1)na parametrze.To zapewni, że pierwsza litera będzie wielka, a reszta pozostanie bez zmian.
-
-
Dodaj publiczną metodę
setFirstName(String firstName), która ustawia imię osoby.-
Upewnij się, że imię zawsze zaczyna się wielką literą.
W tym celu użyj metody wcześniej stworzonej
formatName().
-
-
Dodaj publiczną metodę
setLastName(String lastName), która ustawia nazwisko osoby.- Upewnij się, że nazwisko zawsze zaczyna się wielką literą, używając tej samej metody co w przypadku imienia.
-
Dodaj publiczny konstruktor, który przyjmuje imię i nazwisko jako argumenty i inicjalizuje odpowiednie pola.
- Upewnij się, że imię i nazwisko są poprawnie ustawione przy użyciu metod
setFirstName()isetLastName().
- Upewnij się, że imię i nazwisko są poprawnie ustawione przy użyciu metod
-
Utwórz abstrakcyjną metodę
info(), która będzie zwracać informacje o osobie w formacie zależnym od klasy, która dziedziczy poPerson.- Metoda ta nie powinna być implementowana w klasie
Person, ponieważ każda klasa dziedzicząca poPersonbędzie miała swoją własną implementację.
- Metoda ta nie powinna być implementowana w klasie
Klasa ta powinna być abstrakcyjna, ponieważ nie chcemy, aby była instancjonowana bezpośrednio, ale chcemy, aby była dziedziczona przez inne klasy.
package project.model; // Upewnij się, że katalog model jest w katalogu project
public abstract class Person {
// Pola przechowujące imię i nazwisko
private String firstName;
private String lastName;
// Konstruktor przyjmujący imię i nazwisko
public Person(String firstName, String lastName) {
setFirstName(firstName);
setLastName(lastName);
}
// Getter zwracający imię
public String getFirstName() {
return firstName;
}
// Setter ustawiający imię
public void setFirstName(String firstName) {
this.firstName = formatName(firstName);
}
// Getter zwracający nazwisko
public String getLastName() {
return lastName;
}
// Setter ustawiający nazwisko
public void setLastName(String lastName) {
this.lastName = formatName(lastName);
}
// Metoda zwracająca pełne imię i nazwisko
public String getFullName() {
if (firstName != null && !firstName.isEmpty() && lastName != null && !lastName.isEmpty()) {
return firstName + " " + lastName;
} else if (firstName != null && !firstName.isEmpty()) {
return firstName;
} else if (lastName != null && !lastName.isEmpty()) {
return lastName;
}
return "";
}
// Abstrakcyjna metoda, która będzie zwracać informacje o osobie.
// Nie implementujemy jej tutaj, ponieważ każda klasa dziedzicząca
// po Person będzie miała swoją własną implementację!
public abstract String info();
// Prywatna metoda formatująca imię lub nazwisko
private String formatName(String name) {
if (name != null && !name.isEmpty()) {
return name.substring(0, 1).toUpperCase() + name.substring(1);
}
return name;
}
}Ustawienie pól jako prywatne to fundament enkapsulacji / hermetyzacji, jednej z najważniejszych zasad programowania obiektowego. Oznacza to, że do tych pól nie można uzyskać dostępu bezpośrednio spoza klasy
Person.Dzięki temu możemy kontrolować, jak te pola są modyfikowane i zapewnić, że zawsze będą miały poprawne wartości.
Interfejs LibraryItem będzie oznaczał dowolny przedmiot dostępny w bibliotece do wypożyczenia. Interfejs ten będzie definiował metody, które muszą być zaimplementowane przez klasy reprezentujące różne rodzaje przedmiotów, takie jak książki, magazyny, filmy i muzyka.
W naszym przypadku LibraryItem będzie definiował metodę getTitle(), która zwraca tytuł przedmiotu, oraz metodę info(), która zwraca informacje o przedmiocie w formacie zależnym od klasy, która implementuje ten interfejs.
-
W katalogu
modelstwórz katalogitem.Będzie to katalog, w którym będziemy przechowywać wszystkie interfejsy i klasy reprezentujące przedmioty dostępne w bibliotece i ich właściwości.
-
W katalogu
itemstwórz interfejsLibraryItem. -
Dodaj metodę
getTitle(), która będzie zwracać tytuł przedmiotu jakoString. -
Dodaj domyślną metodę
info(), która będzie zwracałaStringw formacieTytuł: {title}.Ta metoda będzie mogła (ale nie musi) być nadpisana przez klasy implementujące ten interfejs, aby zwracać bardziej szczegółowe informacje o przedmiocie.
package project.model.item;
public interface LibraryItem {
String getTitle();
// Ważne: Metoda info() jest domyślna, co oznacza, że klasy
// implementujące ten interfejs mogą ją nadpisać, ale nie muszą.
default String info() {
return "Tytuł: " + getTitle();
}
}Interfejsy są bardziej elastyczne niż klasy abstrakcyjne, ponieważ pozwalają na implementację wielu interfejsów przez jedną klasę. Dzięki temu możemy łatwiej rozszerzać nasz system o nowe rodzaje przedmiotów w przyszłości, bez konieczności zmiany istniejących klas. Interfejsy również nie narzucają hierarchii dziedziczenia, co daje większą swobodę w projektowaniu systemu.
Zobaczysz, że podczas dalszego rozwoju aplikacji będzie to nam na rękę!
Metoda domyślna to metoda w interfejsie, która ma swoją implementację. Dzięki temu klasy implementujące ten interfejs mogą korzystać z tej implementacji, ale mogą ją również nadpisać, jeśli potrzebują innej logiki.
Metody domyślne definiuje się za pomocą słowa kluczowego
defaultprzed deklaracją metody w interfejsie.
Klasa User będzie reprezentować pojedynczego użytkownika biblioteki, który może wypożyczać lub oddawać przedmioty,
które wcześniej wypożyczył.
User będzie dziedziczył po Person a dodatkowo zawierał swój własny identyfikator oraz listę wypożyczonych przedmiotów.
- W katalogu
modelstwórz klasęUser, która będzie dziedziczyć po klasiePerson. - Dodaj prywatne pole
userIdtypufinal int, które będzie unikalnym identyfikatorem użytkownika. - Dodaj prywatne pole
borrowedItems, które będzie listą przechowującą wypożyczone przedmioty.- Użyj typu
final List<LibraryItem>, będzie to pierwsza relacja między klasami, gdzieUserbędzie przechowywał listę przedmiotów implementujących interfejsLibraryItem.
- Użyj typu
- Dodaj publiczny konstruktor, który przyjmuje imię, nazwisko i identyfikator użytkownika jako argumenty i inicjalizuje odpowiednie pola.
- Dodaj publiczną metodę
getUserId(), która zwraca identyfikator użytkownika. - Dodaj publiczną metodę
getBorrowedItems(), która zwraca listę wypożyczonych przedmiotów. - Dodaj publiczną metodę
addItem(LibraryItem item),hasItem(LibraryItem item)iremoveItem(LibraryItem item).addItem(LibraryItem item)- dodaje przedmiot do listy wypożyczonych przedmiotów.hasItem(LibraryItem item)- sprawdza, czy użytkownik ma dany przedmiot wypożyczony.- Metoda ta powinna zwracać
true, jeśli użytkownik ma dany przedmiot wypożyczony, w przeciwnym raziefalse.
- Metoda ta powinna zwracać
removeItem(LibraryItem item)- usuwa przedmiot z listy wypożyczonych przedmiotów.- Podczas usuwania przedmiotu sprawdź, czy użytkownik posiada ten przedmiot.
Jeśli nie, rzuć wyjątek
IllegalArgumentExceptionz komunikatem"Użytkownik nie posiada: {tytuł}". Zamień{tytuł}na tytuł przedmiotu, który próbuje zostać usunięty.
- Podczas usuwania przedmiotu sprawdź, czy użytkownik posiada ten przedmiot.
Jeśli nie, rzuć wyjątek
- Do stworzenia tych metod przyjrzyj się metodom z interfejsu
List.
- Nadpisz metodę
info()z klasyPerson, aby zwracała informacje o użytkowniku w formacie:
{firstName} {lastName} ({userId}): {X} wypożyczone przedmioty, gdzieXto liczba wypożyczonych przedmiotów przez użytkownika.
public class User extends Person {
private final int userId;
private final List<LibraryItem> borrowedItems = new ArrayList<>();
public User(String firstName, String lastName, int userId) {
super(firstName, lastName);
this.userId = userId;
}
@Override
public String info() {
return this.getFullName() + " (" + userId + "): " + borrowedItems.size() + " wypożyczone przedmioty";
}
public int getUserId() {
return userId;
}
public List<LibraryItem> getBorrowedItems() {
return borrowedItems;
}
public void addItem(LibraryItem item) {
borrowedItems.add(item);
}
public boolean hasItem(LibraryItem item) {
return borrowedItems.contains(item);
}
public void removeItem(LibraryItem item) {
if (!hasItem(item)) {
throw new IllegalArgumentException("Użytkownik nie posiada: " + item.getTitle());
}
borrowedItems.remove(item);
}
}W ten sposób otrzymaliśmy pierwszą relację dziedziczenia między klasami, gdzie User dziedziczy po Person:
Słowo kluczowe
finaloznacza, że pole nie może być zmienione po jego inicjalizacji.Często używamy
finalw polach, które powinny być stałe i niezmienne, takie jak identyfikatory użytkowników czy tytuły książek.
Adnotacja
@Overridejest używana do oznaczenia, że metoda w klasie potomnej nadpisuje metodę z klasy nadrzędnej.Służy jako informacja dla kompilatora i innych programistów, że metoda ta jest zamierzona do nadpisania metody z klasy nadrzędnej.
Nie, ale warto. Adnotacja jasno wskazuje, że metoda jest nadpisywana i pozwala uniknąć błędów, jeśli nazwa metody lub jej sygnatura zostanie zmieniona w klasie nadrzędnej.
Pierwszym rodzajem przedmiotu możliwym do wypożyczenia będzie książka, która powinna implementować interfejs LibraryItem
i zawierać dodatkowe informacje specyficzne dla książek, takie jak autor, kod ISBN i liczba stron.
Aby jeszcze bardziej urozmaicić naszą strukturę, stworzymy Book jako klasę abstrakcyjną, która będzie dziedziczona przez inne konkretne rodzaje książek.
-
W katalogu
itemutwórz podkatalogbook.Będzie to katalog, w którym będziemy przechowywać klasy reprezentujące różne rodzaje książek.
-
W katalogu
bookstwórz klasę abstrakcyjnąBook, która będzie implementować interfejsLibraryItem. -
Dodaj prywatne pola:
titletypufinal String- tytuł książki.authortypufinal String- autor książki.isbntypufinal String- numer ISBN książki.pagestypufinal int- liczba stron w książce.
-
Dodaj publiczny konstruktor, który przyjmuje tytuł, autora, ISBN i liczbę stron jako argumenty i inicjalizuje odpowiednie pola.
-
Zaimplementuj metodę
getTitle()z interfejsuLibraryItem, która zwraca tytuł książki. -
Dodaj dodatkowe publiczne metody
getAuthor(),getIsbn()igetPages(), które zwracają odpowiednio autora, ISBN i liczbę stron książki. -
Nadpisz metodę
info()z interfejsuLibraryItem, aby zwracała informacje o książce w formacie:
"{tytuł}", {autor} (strony: {strony}, ISBN: {isbn}).- Zamień
{tytuł},{autor},{strony}i{isbn}na odpowiednie wartości z pól klasy. - Aby wypisać znak
"w Stringu, użyj znaku\przed cudzysłowem, np.\".
- Zamień
public abstract class Book implements LibraryItem {
private final String title;
private final String author;
private final String isbn;
private final int pages;
public Book(String title, String author, String isbn, int pages) {
this.title = title;
this.author = author;
this.isbn = isbn;
this.pages = pages;
}
@Override
public String getTitle() {
return title;
}
@Override
public String info() {
return "\"" + getTitle() + "\", " + author + " (strony: " + pages + ", ISBN: " + isbn + ")";
}
public String getAuthor() {
return author;
}
public String getIsbn() {
return isbn;
}
public int getPages() {
return pages;
}
}Klasa
Bookjest abstrakcyjna, ponieważ nie chcemy, aby była instancjonowana bezpośrednio, ale posiadała wspólne pola i metody dla wszystkich rodzajów książek, które będą dziedziczyć po tej klasie. Dzięki temu możemy łatwiej rozszerzać nasz system o nowe rodzaje książek w przyszłości, bez konieczności duplikowania kodu.
Mając taką podstawę, możemy teraz stworzyć konkretne rodzaje książek, które będą dziedziczyć po klasie Book.
Pierwszymi konkretnymi rodzajami książek, które stworzymy, będą:
Textbook- podręcznikNovel- powieśćManga- japoński komiks
Każda z tych klas będzie dziedziczyć po klasie Book i dodawać swoje specyficzne pola oraz metody.
- W katalogu
bookstwórz klasęTextbook, która będzie dziedziczyć po klasieBook. - Dodaj prywatne pole
subjecttypuString, które będzie przechowywać przedmiot, którego dotyczy podręcznik. - Dodaj publiczny konstruktor, który przyjmuje tytuł, autora, ISBN, liczbę stron i przedmiot jako argumenty i inicjalizuje odpowiednie pola.
- Z racji, że rozszerzamy abstrakcyjną klasę
Book, musimy wywołać konstruktor klasy nadrzędnejBookw konstruktorze klasyTextbook. - Użyj słowa kluczowego
superdo wywołania konstruktora klasyBookz odpowiednimi argumentami.super(title, author, isbn, pages);
- Słowo kluczowe
superodnosi się do klasy nadrzędnej i pozwala na wywołanie jej konstruktora lub metod.
- Z racji, że rozszerzamy abstrakcyjną klasę
- Dodaj publiczną metodę
getSubject(), która zwraca przedmiot podręcznika.
public class Textbook extends Book {
private final String subject;
public Textbook(String title, String author, String isbn, int pages, String subject) {
super(title, author, isbn, pages);
this.subject = subject;
}
public String getSubject() {
return subject;
}
}Słowo kluczowe super odnosi się do klasy nadrzędnej (w tym przypadku
Book). Wywołaniesuper(...)w konstruktorze klasy potomnej (Textbook) służy do wywołania konstruktora z klasy nadrzędnej.Musi to być pierwsza instrukcja w konstruktorze, ponieważ Java wymaga, aby obiekt klasy nadrzędnej został w pełni zainicjalizowany, zanim zaczniemy inicjalizować pola specyficzne dla klasy potomnej. To zapewnia, że wszystkie odziedziczone pola (jak title czy author) mają już swoje wartości, zanim przejdziemy do ustawiania pola subject.
- W katalogu
bookstwórz klasęNovel, która będzie dziedziczyć po klasieBook. - Dodaj prywatne pole
genretypuString, które będzie przechowywać gatunek powieści. - Dodaj publiczny konstruktor, który przyjmuje tytuł, autora, ISBN, liczbę stron i gatunek jako argumenty i inicjalizuje odpowiednie pola.
- Dodaj publiczną metodę
getGenre(), która zwraca gatunek powieści.
public class Novel extends Book {
private final String genre;
public Novel(String title, String author, String isbn, int pages, String genre) {
super(title, author, isbn, pages);
this.genre = genre;
}
public String getGenre() {
return genre;
}
}- W katalogu
bookstwórz klasęManga, która będzie dziedziczyć po klasieBook. - Dodaj prywatną listę
illustratorstypuList<String>, która będzie przechowywać imiona i nazwiska ilustratorów mangi. - Dodaj publiczny konstruktor, który przyjmuje tytuł, autora, ISBN, liczbę stron i listę ilustratorów jako argumenty i inicjalizuje odpowiednie pola.
- Dodaj publiczną metodę
getIllustrators(), która zwraca listę ilustratorów mangi.
public class Manga extends Book {
private final List<String> illustrators;
public Manga(String title, String author, String isbn, int pages, List<String> illustrators) {
super(title, author, isbn, pages);
this.illustrators = illustrators;
}
public List<String> getIllustrators() {
return illustrators;
}
}Zauważ, że nie nadpisujemy metody
info(), ponieważ klasaBookjuż ją implementuje i zwraca wszystkie potrzebne informacje. Jeśli jednak chcielibyśmy dodać dodatkowe informacje specyficzne dla danego rodzaju książki, moglibyśmy nadpisać tę metodę w odpowiedniej klasie.
W te sposób otrzymaliśmy trzy konkretne rodzaje książek, które dziedziczą po klasie Book i implementują swoje specyficzne pola i metody.
Dzięki temu podejściu możemy łatwo rozszerzać nasz system o nowe rodzaje książek w przyszłości, bez konieczności duplikowania kodu. Każda nowa klasa książki będzie dziedziczyć po klasie
Booki implementować swoje specyficzne pola i metody, co pozwoli nam na łatwe zarządzanie różnymi rodzajami książek w bibliotece.
Aby przetestować nasze klasy User i Book, stwórz przykładowe obiekty w istniejącej już klasie Main w katalogu project.
-
W katalogu
projectotwórz plikMain.java. -
W metodzie
mainstwórz obiektUserz przykładowymi danymi, np.:User janKowalski = new User("Jan", "Kowalski", 1); User adamNowak = new User("Adam", "Nowak", 2);
-
Stwórz kilka obiektów
Textbook,NoveliMangaz przykładowymi danymi, np.:Textbook textbook = new Textbook("Analiza Matematyczna", "Jan Nowak", "978-83-123456-7", 300, "Matematyka"); Novel novel = new Novel("W pustyni i w puszczy", "Henryk Sienkiewicz", "978-83-123456-8", 400, "Przygodowa"); Manga manga = new Manga("Naruto", "Masashi Kishimoto", "978-83-123456-9", 200, List.of("Masashi Kishimoto", "Yasuharu Takanashi"));
-
Dodaj te przedmioty do wypożyczonych przedmiotów użytkownika, np.:
janKowalski.addItem(textbook); janKowalski.addItem(novel); adamNowak.addItem(manga);
-
Wyświetl informacje o użytkownikach i wypożyczonych przedmiotach, np.:
System.out.println(janKowalski.info()); System.out.println(adamNowak.info()); System.out.println(textbook.info()); System.out.println(novel.info()); System.out.println(manga.info());
-
Uruchom program
Main, aby sprawdzić, czy wszystko działa poprawnie i czy informacje o użytkownikach i przedmiotach są wyświetlane zgodnie z oczekiwaniami.Wynik powinien wyglądać w taki sposób:
Jan Kowalski (1): 2 wypożyczone przedmioty Adam Nowak (2): 1 wypożyczone przedmioty "Analiza Matematyczna", Jan Nowak (strony: 300, ISBN: 978-83-123456-7) "W pustyni i w puszczy", Henryk Sienkiewicz (strony: 400, ISBN: 978-83-123456-8) "Naruto", Masashi Kishimoto (strony: 200, ISBN: 978-83-123456-9)
Jeżeli wszystko działa poprawnie, to mamy już podstawową strukturę naszego systemu bibliotecznego, która pozwala na tworzenie użytkowników i książek.
Kolejnym rodzajem przedmiotu, który dodamy do naszego systemu bibliotecznego, będzie magazyn.
Magazyn będzie również implementował interfejs LibraryItem i będzie zawierał dodatkowe informacje specyficzne dla magazynów, takie jak autor,
kod ISSN i numer wydania.
-
W katalogu
itemstwórz podkatalogmagazine.Będzie to katalog, w którym będziemy przechowywać klasy reprezentujące magazyny.
-
W katalogu
magazinestwórz abstrakcyjną klasęMagazine, która będzie implementować interfejsLibraryItem. -
Dodaj prywatne pola:
titletypufinal String- tytuł magazynu.authortypufinal String- autor magazynu.issntypufinal String- numer ISSN magazynu.issueNumbertypufinal int- numer wydania magazynu.
-
Dodaj publiczny konstruktor, który przyjmuje tytuł, autora, ISSN i numer wydania jako argumenty i inicjalizuje odpowiednie pola.
-
Dodaj publiczne metody
getTitle(),getAuthor(),getIssn()igetIssueNumber(), które zwracają odpowiednio tytuł, autora, ISSN i numer wydania magazynu. -
Nadpisz metodę
info()z interfejsuLibraryItem, aby zwracała informacje o magazynie w formacie:"{title}", wydanie {issueNumber}, {author} (ISSN: {issn}).- Zamień
{title},{issueNumber},{author}i{issn}na odpowiednie wartości z pól klasy. - Aby wypisać znak
"w Stringu, użyj znaku\przed cudzysłowem, np.\".
- Zamień
Super, oddzieliliśmy klasę Magazine od innych przedmiotów, co tak jak w przypadku Book pozwoli nam na łatwe dodawanie nowych rodzajów magazynów.
Kolejnymi konkretnymi rodzajami magazynów, które stworzymy, będą:
NewsMagazine- magazyn informacyjny.ScienceMagazine- magazyn naukowy.
Podobnie jak w Book, każda z tych klas będzie dziedziczyć po klasie Magazine i dodawać swoje specyficzne pola oraz metody.
- W katalogu
magazinestwórz klasęNewsMagazine, która będzie dziedziczyć po klasieMagazine. - Dodaj prywatne pole
featuredTopicstypuList<String>, które będzie przechowywać tematy przewodnie magazynu. - Dodaj publiczny konstruktor, który przyjmuje tytuł, autora, ISSN, numer wydania i tematy przewodnie jako argumenty i inicjalizuje odpowiednie pola.
- Dodaj publiczną metodę
getFeaturedTopics(), która zwracafeaturedTopics.
- W katalogu
magazinestwórz klasęScienceMagazine, która będzie dziedziczyć po klasieMagazine. - Dodaj prywatne pole
fieldOfStudytypuString, które będzie przechowywać dziedzinę nauki, której dotyczy magazyn. - Dodaj publiczny konstruktor, który przyjmuje tytuł, autora, ISSN, numer wydania i dziedzinę nauki jako argumenty i inicjalizuje odpowiednie pola.
- Dodaj publiczną metodę
getFieldOfStudy(), która zwracafieldOfStudy.
Aby przetestować nasze klasy Magazine, NewsMagazine i ScienceMagazine, stwórz przykładowe obiekty w istniejącej już klasie Main w katalogu project.
- W metodzie
mainstwórz obiektMagazinez przykładowymi danymi, np.:Magazine scienceMagazine = new ScienceMagazine("Nature", "John Doe", "1234-5678", 1, "Biologia"); Magazine newsMagazine = new NewsMagazine("The Times", "Jane Smith", "8765-4321", 2, List.of("Polityka", "Ekonomia"));
- Następnie dodaj te magazyny do wypożyczonych przedmiotów użytkownika, np.:
janKowalski.addItem(scienceMagazine); adamNowak.addItem(newsMagazine);
- Dodaj wyświetlanie informacji o magazynach, np.:
System.out.println(scienceMagazine.info()); System.out.println(newsMagazine.info()); System.out.println("Szczegółowe informacje o " + scienceMagazine.getTitle() + ":"); System.out.println(" Tytuł: " + scienceMagazine.getTitle()); System.out.println(" Autor: " + scienceMagazine.getAuthor()); System.out.println(" ISSN: " + scienceMagazine.getIssn()); System.out.println(" Numer wydania: " + scienceMagazine.getIssueNumber()); System.out.println(" Dziedzina nauki: " + scienceMagazine.getFieldOfStudy());
- Teraz oprócz informacji o użytkownikach, powinieneś zobaczyć również informacje o magazynach:
Jan Kowalski (1): 2 wypożyczone przedmioty Adam Nowak (2): 2 wypożyczone przedmioty "Nature", wydanie 1, John Doe (ISSN: 1234-5678) "The Times", wydanie 2, Jane Smith (ISSN: 8765-4321) Szczegółowe informacje o Nature: Tytuł: Nature Autor: John Doe ISSN: 1234-5678 Numer wydania: 1 Dziedzina nauki: Biologia
Jeżeli wszystko działa poprawnie, to udało nam się dodać nowe rodzaje przedmiotów do naszego systemu bibliotecznego.
Zauważ, że zarówno klasa Book, jak i klasa Magazine mają pole author, które przechowuje autora przedmiotu.
Aby uniknąć duplikacji kodu i zwiększyć elastyczność naszego systemu, możemy stworzyć interfejs Authored, który będzie definiował autorów danego przedmiotu.
W ten sposób stworzyć część wspólną dla wszystkich przedmiotów, które mają autorów, niezależnie od tego, czy są to książki, magazyny czy inne przedmioty.
Dodatkowo co w przypadku, jeżeli przedmiot ma więcej niż jednego autora?
Aby to obsłużyć, możemy stworzyć klasę Author, która będzie reprezentować pojedynczego autora i będzie zawierać jego imię, nazwisko oraz pseudonim.
Interfejs Authored będzie zawierał metodę getAuthors(), która zwraca listę autorów (List<Author>).
W ten sposób będziemy mogli łatwo zarządzać autorami przedmiotów w bibliotece, niezależnie od tego, czy są to książki, magazyny czy inne przedmioty.
Refaktoryzacja to proces ulepszania struktury istniejącego kodu bez zmieniania jego zewnętrznego zachowania. Wyobraź sobie, że masz działający silnik, ale jego wewnętrzne części są chaotycznie ułożone. Rozbierasz go i składasz na nowo, używając lepszych, bardziej uporządkowanych komponentów. Z zewnątrz silnik nadal robi to samo, ale teraz jest łatwiejszy w utrzymaniu, naprawie i dalszej rozbudowie.
W naszym przypadku refaktoryzujemy sposób obsługi autorów, aby kod był czystszy, unikał powtórzeń i był gotowy na przyszłe zmiany (np. obsługę wielu autorów). To jedna z najważniejszych umiejętności każdego programisty.
W naszym przypadku "refaktoryzujemy" sposób obsługi autorów, aby kod był czystszy, unikał powtórzeń i był gotowy na przyszłe zmiany (np. obsługę wielu autorów). To jedna z najważniejszych umiejętności każdego programisty.
Żeby podążać za zasadą pojedynczej odpowiedzialności, możemy stworzyć klasę Creator, która będzie reprezentować osobę tworzącą przedmiot biblioteczny.
Klasa ta będzie dziedziczyć po klasie Person i będzie zawierać dodatkowe informacje specyficzne dla twórcy, takie jak pseudonim, data urodzenia i data śmierci (jeśli dotyczy).
- W katalogu
itemstwórz klasęCreator, która będzie dziedziczyć po klasiePerson. - Dodaj prywatne pole
pseudonymtypufinal String, które będzie przechowywać pseudonim twórcy. - Dodaj prywatne pole
birthDatetypufinal LocalDate, które będzie przechowywać datę urodzenia twórcy. - Dodaj prywatne pole
deathDatetypuLocalDate, które będzie przechowywać datę śmierci twórcy. - Dodaj publiczny konstruktor, który przyjmuje imię, nazwisko, pseudonim, datę urodzenia i datę śmierci (jeśli dotyczy) jako argumenty i inicjalizuje odpowiednie pola.
- Użyj słowa kluczowego
superdo wywołania konstruktora klasyPersonz odpowiednimi argumentami.
- Użyj słowa kluczowego
- Dodaj publiczne metody
getPseudonym(),getBirthDate()igetDeathDate(), które zwracają odpowiednio pseudonim, datę urodzenia i datę śmierci twórcy. - Dodaj publiczną metodę dla settera
setDeathDate(LocalDate deathDate), która ustawia datę śmierci twórcy.- Metoda ta powinna przyjmować argument
deathDatetypuLocalDatei ustawiać poledeathDatena tę wartość. - Jeśli twórca jeszcze żyje, pole
deathDatepowinno pozostaćnull.
- Metoda ta powinna przyjmować argument
- Nadpisz metodę
getFullname()z klasyPerson, aby zwracała pseudonim autora, jeśli jest dostępny:- Jeśli pseudonim jest dostępny, zwróć
{pseudonym}. - Jeśli pseudonim nie jest dostępny, zwróć
{firstName} {lastName}. - Zamień
{firstName},{lastName}i{pseudonym}na odpowiednie wartości z pól klasy. 9Nadpisz metodęinfo()z klasyPerson, aby zwracała informacje o twórcy w formacie: - Jeśli autor jeszcze żyje:
{pseudonym} ({firstName} {lastName}), {birthDate}.
- Jeśli autor nie żyje:
{pseudonym} ({firstName} {lastName}), {birthDate}-{deathDate}.
- Zamień
{pseudonym},{firstName},{lastName},{birthDate}i{deathDate}na odpowiednie wartości z pól klasy.
- Jeśli pseudonim jest dostępny, zwróć
Klasa
Creatorpozwoli nam na odseparowanie twórców od innych osób, takich jak użytkownicy biblioteki. Oprócz tego da nam solidny entry-point do dalszego rozwijania systemu, np. dodawania nowych rodzajów twórców,
Ponieważ twórca może jeszcze żyć. :P
Możemy więc nie inicjalizować tego pola w konstruktorze, a pozostawić je jako
null, jeśli autor jeszcze żyje. A w późniejszym czasie, jeśli autor umrze, możemy ustawić tę datę.
Posiadając klasę Creator, możemy teraz stworzyć klasę Author, która będzie reprezentować autora książek i magazynów.
Klasa ta będzie dziedziczyć po klasie Creator i będzie zawierać dodatkowe informacje specyficzne dla autora, takie jak
języki, w których tworzy autor.
- W katalogu
itemstwórz klasęAuthor, która będzie dziedziczyć po klasieCreator. - Dodaj prywatne pole
languagestypuList<String>, które będzie przechowywać języki, w których tworzy autor. - Dodaj publiczny konstruktor, który przyjmuje imię, nazwisko, pseudonim, datę urodzenia, datę śmierci
(jeśli dotyczy) i listę języków jako argumenty i inicjalizuje odpowiednie pola.
- Użyj słowa kluczowego
superdo wywołania konstruktora klasyCreatorz odpowiednimi argumentami.
- Użyj słowa kluczowego
- Dodaj publiczną metodę
getLanguages(), która zwraca listę języków, w których tworzy autor. - Nadpisz metodę
info()z klasyCreator, aby zwracała również języki:- Skorzystaj z metody
info()z klasyCreatori dodaj do niej informacje o językach, w których tworzy autor w formacie:{info() z Creator}, języki: {languages}.
- Zamień
{info() z Creator}i{languages}na odpowiednie wartości z pól klasy.
- Skorzystaj z metody
Możemy użyć słowa kluczowego
superdo wywołania metodyinfo()z klasy nadrzędnejCreator.super.info();Słowo kluczowe
superodnosi się do klasy nadrzędnej i pozwala na wywołanie jej metod lub pól.
Aby umożliwić przedmiotom bibliotecznym posiadanie wielu autorów, stworzymy interfejs Authored,
który będzie definiował metody związane z autorami.
- W katalogu
itemstwórz interfejsAuthored. - Dodaj metodę
getAuthors(), która będzie zwracać listę autorów (List<Author>). - Dodaj domyślną metodę
getAuthorNames(), która zwraca ciąg znaków z imionami i nazwiskami autorów, oddzielonymi przecinkami.- Metoda ta powinna wykorzystywać metodę
getFullName()z klasyCreatordo uzyskania pełnych imion i nazwisk autorów oraz ich pseudonimów, jeśli są dostępne. - Zadbaj o to, aby autorzy byli poprawnie oddzieleni przecinkami, np. żeby nie było przecinka na końcu listy.
- Metoda ta powinna wykorzystywać metodę
Kliknij aby odsłonić
Do oddzielenia autorów możesz skorzystać z pętli
foriStringBuilderlub strumieni.StringBuilder authors = new StringBuilder(); for (int i = 0; i < getAuthors().size(); i++) { Author author = getAuthors().get(i); authors.append(author.getFullName()); if (i < getAuthors().size() - 1) { authors.append(", "); } }W tym przykładzie używamy pętli
fordo iteracji po liście autorów i budowania ciągu znaków za pomocąStringBuilderz ich pseudonimami lub pełnymi imionami i nazwiskami, oddzielając ich przecinkami.String authors = authorsList.stream() .map(author -> author.getPseudonym() != null ? author.getPseudonym() : author.getFullName()) .collect(Collectors.joining(", "));W tym przykładzie używamy strumieni do przetworzenia listy autorów i połączenia ich pseudonimów lub pełnych imion i nazwisk w jeden ciąg znaków, oddzielony przecinkami.
W obu przypadkach, jeśli autor nie ma pseudonimu, używamy metody `getFullName()` z klasy `Person`, aby uzyskać jego pełne imię i nazwisko.
Teraz możemy zrefaktoryzować nasze klasy Book i Magazine, aby implementowały interfejs Authored i zamiast pojedynczego pola author miały listę autorów.
- Zaktualizuj klasę
Book, aby implementowała interfejsAuthored. - Zamiast pojedynczego pola
author, dodaj prywatne poleauthorstypuList<Author>. - Usuń pole
authororaz metodęgetAuthor(), zastąp je metodągetAuthors(), która zwracaauthors. - Zaktualizuj konstruktory klas
Book,Textbook,NoveliManga, aby przyjmowały listę autorów jako argumenty i inicjalizowały poleauthors. - Zaktualizuj metodę
info()w klasieBook, aby uwzględniała wszystkich autorów książki:-
Zamiast jednego autora, metoda powinna zwracać wszystkich autorów w formacie:
"{tytuł}", {autorzy} (strony: {strony}, ISBN: {isbn}), gdzie{autorzy}to lista autorów oddzielona przecinkami.- Skorzystaj z metody
getAuthorNames()z interfejsuAuthored, aby uzyskać listę autorów w odpowiednim formacie.
- Skorzystaj z metody
-
- Upewnij się, że nie zapomniałeś zaktualizować wszystkich miejsc, gdzie wcześniej używano pola
author.
Nie zapomnij również zaktualizować pola illustrators w klasie Manga, aby było typu List<Author> zamiast List<String>.
Powtórz podobne kroki dla klas Magazine.
Aby przetestować nasze ulepszenia, zaktualizuj klasę Main w katalogu project.
- W katalogu
projectotwórz plikMain.java. - Zaktualizuj tworzenie obiektów
Textbook,NoveliManga, aby używały listy autorów zamiast pojedynczego autora, np.:List<Author> textbookAuthors = List.of(new Author("Jan", "Nowak", "J.N.", LocalDate.of(1992, 1, 3), null, List.of("Polski", "Angielski"))); Textbook textbook = new Textbook("Analiza Matematyczna", textbookAuthors, "978-83-123456-7", 300, "Matematyka"); List<Author> novelAuthors = List.of(new Author("Henryk", "Sienkiewicz", null, LocalDate.of(1846, 5, 5), LocalDate.of(1916, 11, 15), List.of("Polski"))); Novel novel = new Novel("W pustyni i w puszczy", novelAuthors, "978-83-123456-8", 400, "Przygodowa"); List<Author> mangaAuthors = List.of( new Author("Masashi", "Kishimoto", null, LocalDate.of(1974, 11, 8), null, List.of("Japoński")), new Author("Yasuharu", "Takanashi", null, LocalDate.of(1963, 4, 13), null, List.of("Japoński"))); List<Author> illustrators = List.of(mangaAuthors.getFirst()); Manga manga = new Manga("Naruto", mangaAuthors, "978-83-123456-9", 200, illustrators); List<Author> scienceMagazineAuthors = List.of(new Author("John", "Doe", null, null, null, Collections.emptyList())); Magazine scienceMagazine = new ScienceMagazine("Nature", scienceMagazineAuthors, "1234-5678", 1, "Biology"); List<Author> newsMagazineAuthors = List.of(new Author("Jane", "Smith", null, null, null, Collections.emptyList())); Magazine newsMagazine = new NewsMagazine("The Times", newsMagazineAuthors, "8765-4321", 2, List.of("Politics", "Economy"));
- Dodaj te przedmioty do wypożyczonych przedmiotów użytkownika, np.:
janKowalski.addItem(textbook); janKowalski.addItem(novel); adamNowak.addItem(manga); adamNowak.addItem(scienceMagazine); adamNowak.addItem(newsMagazine);
- Wyświetl informacje o użytkownikach i wypożyczonych przedmiotach, np.:
System.out.println(janKowalski.info()); System.out.println(adamNowak.info()); System.out.println(textbook.info()); System.out.println(novel.info()); System.out.println(manga.info()); System.out.println(scienceMagazine.info()); System.out.println(newsMagazine.info());
Po uruchomieniu Main, powinieneś zobaczyć informacje o użytkownikach i przedmiotach, które zawierają listę autorów
oraz ich pseudonimy, jeśli są dostępne. Wynik powinien wyglądać mniej więcej tak:
Jan Kowalski (1): 3 wypożyczone przedmioty
Adam Nowak (2): 2 wypożyczone przedmioty
"Analiza Matematyczna", J.N. (strony: 300, ISBN: 978-83-123456-7)
"W pustyni i w puszczy", Henryk Sienkiewicz (strony: 400, ISBN: 978-83-123456-8)
"Naruto", Masashi Kishimoto, Yasuharu Takanashi (strony: 200, ISBN: 978-83-123456-9)
"Nature", wydanie 1, John Doe (ISSN: 1234-5678)
"The Times", wydanie 2, Jane Smith (ISSN: 8765-4321)
Jeżeli program się kompiluje i wyświetla poprawne informacje o użytkownikach i przedmiotach, to znaczy, że nasze ulepszenia zostały poprawnie zaimplementowane.
Dzięki temu ulepszeniu możemy łatwo zarządzać autorami przedmiotów w bibliotece, niezależnie od tego, czy są to książki, magazyny czy inne przedmioty. Dodatkowo, jeśli w przyszłości dodamy nowe rodzaje przedmiotów, które również będą miały autorów, będziemy mogli po prostu zaimplementować interfejs
Authoredi skorzystać z tej samej logiki zarządzania autorami.
To zależy od kontekstu i wymagań projektu. Jeśli wszystkie przedmioty, które mają autorów, będą miały wspólne pola i metody, to abstrakcyjna klasa może być lepszym rozwiązaniem. Jednak w naszym przypadku interfejs
Authoredjest wystarczający, ponieważ nie mamy wspólnych pól ani metod poza listą autorów.
Podobno w bibliotece można wypożyczać nie tylko książki i magazyny, ale także muzykę. W takim razie dodamy nowy rodzaj przedmiotu, który będzie reprezentować utwór muzyczny.
Najpierw zastanówmy się, jakie informacje powinien zawierać taki utwór muzyczny.
- Tytuł utworu — można skorzystać z pola
titlez interfejsuLibraryItem. - Autorzy utworu — można skorzystać z interfejsu
Authored, który już mamy. - Czas trwania utworu — pole unikalne dla utworu muzycznego, które będzie przechowywać czas trwania w sekundach.
- Gatunek muzyczny — również unikalne, które będzie przechowywać gatunek muzyczny utworu.
Czas trwania utworu możemy przechowywać jako int, który będzie reprezentować liczbę sekund.
Stworzymy w tym celu interfejs Playable, będzie on oznaczał przedmiot, który można odtwarzać.
Dla gatunku muzycznego możemy użyć specjalnego Enum'a MusicGenre, który będzie zawierał różne gatunki muzyczne,
takie jak:
- ROCK
- POP
- JAZZ
- CLASSICAL
- HIP_HOP
- ELECTRONIC
- METAL
Będzie to wygodne podejście, ponieważ pozwoli nam na łatwe dodawanie nowych gatunków muzycznych w przyszłości, a także zapewni, że gatunek muzyczny będzie zawsze poprawny i zgodny z naszymi oczekiwaniami.
- W katalogu
itemstwórz interfejsPlayable. - Dodaj metodę
getDuration(), która będzie zwracać czas trwania utworu w sekundach (int). - Dodaj domyślną metodę
formatDuration(), która będzie zwracać sformatowany czas trwania utworu w formacie(hh:mm:ss).- Metoda ta powinna wykorzystywać metodę
getDuration()do uzyskania czasu trwania w sekundach i konwertować go na poprawny format czasu.- Spróbuj wykorzystać metodę
String.format()do prostego formatowania czasu.
- Spróbuj wykorzystać metodę
- Możesz użyć operatora dzielenia i reszty z dzielenia, aby uzyskać minuty i sekundy.
- Metoda ta powinna wykorzystywać metodę
- W katalogu
itemstwórz podkatalogmusic. - W katalogu
musicstwórz EnumMusicGenre. - Dodaj wartości, które będą reprezentować różne gatunki muzyczne:
ROCKPOPJAZZCLASSICALHIP_HOPELECTRONICMETAL
Enum pozwala zdefiniować z góry ustalony, zamknięty zbiór stałych wartości. Jest to znacznie lepsze rozwiązanie niż używanie Stringów ("ROCK", "POP") z kilku powodów:
- Bezpieczeństwo typów: Masz gwarancję, że zmienna typu
MusicGenremoże przyjąć tylko jedną ze zdefiniowanych wartości. Nie da się przez pomyłkę przypisać jej wartościString.- Czytelność: Enumy są bardziej czytelne i zrozumiałe dla innych programistów.
- Rozszerzalność: Jeśli w przyszłości będziesz chciał dodać nowe gatunki muzyczne, wystarczy dodać nową wartość do Enuma, a nie martwić się o poprawność danych.
- W katalogu
musicstwórz abstrakcyjną klasęMusicTrack, która będzie implementować interfejsyLibraryItem,AuthorediPlayable. - Dodaj prywatne pola:
titletypufinal String- tytuł utworu.authorstypufinal List<Author>- lista autorów utworu.durationtypufinal int- czas trwania utworu w sekundach.genretypufinal MusicGenre- gatunek muzyczny utworu.
- Dodaj publiczny konstruktor, który przyjmuje tytuł, listę autorów, czas trwania i gatunek muzyczny jako argumenty i inicjalizuje odpowiednie pola.
- Dodaj lub nadpisz publiczne metody:
getTitle()- zwraca tytuł utworu.getAuthors()- zwraca listę autorów utworu.getDuration()- zwraca czas trwania utworu w sekundach.getGenre()- zwraca gatunek muzyczny utworu.
- Nadpisz metodę
info()z interfejsuLibraryItem, aby zwracała informacje o utworze w formacie:{autorzy} - {title} ({duration}) :: {genre}, gdzie{autorzy}to lista autorów oddzielona przecinkami.- Skorzystaj z metody
getAuthorNames()z interfejsuAuthoredoraz metodyformatDuration()z interfejsuPlayable, aby uzyskać odpowiednie wartości.
Kolejnymi konkretnymi rodzajami utworów muzycznych, które stworzymy, będą:
VocalTrack- utwór muzyczny z wokalem.InstrumentalTrack- utwór muzyczny bez wokalu.
- W katalogu
musicstwórz klasęVocalTrack, która będzie dziedziczyć po klasieMusicTrack. - Dodaj prywatne pola:
vocaliststypuList<Author>- lista wokalistów utworu.isExplicittypuboolean- informacja, czy utwór zawiera wulgarny lub kontrowersyjny tekst.languagetypuString- język, w którym jest wykonywany utwór.
- Dodaj publiczny konstruktor, który przyjmuje tytuł, listę autorów, czas trwania, gatunek muzyczny, listę wokalistów, informację o wulgarnym tekście i język jako argumenty i inicjalizuje odpowiednie pola.
- Dodaj publiczne metody:
getVocalists()- zwraca listę wokalistów utworu.isExplicit()- zwraca informację, czy utwór zawiera wulgarny lub kontrowersyjny tekst.getLanguage()- zwraca język, w którym jest wykonywany utwór.
- W katalogu
musicstwórz klasęInstrumentalTrack, która będzie dziedziczyć po klasieMusicTrack. - Dodaj prywatne pola:
instrumentstypuList<String>- lista instrumentów użytych w utworze.isElectronictypuboolean- informacja, czy utwór jest elektroniczny.
- Dodaj publiczny konstruktor, który przyjmuje tytuł, listę autorów, czas trwania, gatunek muzyczny, listę instrumentów i informację o elektroniczności jako argumenty i inicjalizuje odpowiednie pola.
- Dodaj publiczne metody:
getInstruments()- zwraca listę instrumentów użytych w utworze.isElectronic()- zwraca informację, czy utwór jest elektroniczny.
Aby przetestować nasze klasy MusicTrack, zaktualizuj klasę Main w katalogu project.
- W katalogu
projectotwórz plikMain.java. - Stwórz obiekt
MusicTrackz przykładowymi danymi, np.:List<Author> musicAuthors = List.of( new Author(null, null, "rów babicze", null, null, List.of("Polski"))); List<Author> vocalists = List.of( new Author(null, null, "Bardal", null, null, List.of("Polski"))); VocalTrack musicTrack = new VocalTrack("W GORĄCEJ WODZIE COMPANY", musicAuthors, 167, MusicGenre.HIP_HOP, vocalists, true, "Polski");
- Dodaj ten utwór do wypożyczonych przedmiotów użytkownika, np.:
janKowalski.addItem(musicTrack);
- Dodaj wyświetlanie informacji o utworze, np.:
System.out.println(musicTrack.info()); System.out.println("Szczegółowe informacje o " + musicTrack.getTitle() + ":"); System.out.println(" Tytuł: " + musicTrack.getTitle()); System.out.println(" Autorzy: " + musicTrack.getAuthorsNames()); System.out.println(" Czas trwania: " + musicTrack.formatDuration()); System.out.println(" Gatunek: " + musicTrack.getGenre()); System.out.println(" Wokaliści: " + musicTrack.getVocalists()); System.out.println(" Wulgarny: " + musicTrack.isExplicit()); System.out.println(" Język: " + musicTrack.getLanguage());
- Teraz oprócz informacji o użytkownikach, powinieneś zobaczyć również informacje o utworach muzycznych:
rów babicze - W GORĄCEJ WODZIE COMPANY (00:02:47) :: HIP_HOP Szczegółowe informacje o W GORĄCEJ WODZIE COMPANY: Tytuł: W GORĄCEJ WODZIE COMPANY Autorzy: rów babicze Czas trwania: 00:02:47 Gatunek: HIP_HOP Wokaliści: [project.model.item.Author@2328c243] Wulgarny: true Język: Polski
Jak można zauważyć, lista wokalistów jest wyświetlana jako referencja do obiektu Author, a nie jako ciąg znaków.
Aby poprawić to wyświetlanie, możemy nadpisać metodę toString() w klasie Creator, aby zwracała pełne imię i nazwisko
lub pseudonim autora.
W klasie Creator nadpisz metodę toString(), aby zwracała pełne imię i nazwisko lub pseudonim autora (możesz skorzystać z metody getFullName()).
Metoda toString() to jedna z kilku metod bazowej klasy Object, która jest używana do reprezentacji obiektu w postaci ciągu znaków, więc nadpisanie jej pozwoli nam
na łatwe wyświetlanie informacji o autorze w konsoli lub w innych miejscach, gdzie obiekt jest konwertowany na ciąg znaków.
Metoda
toString()jest dziedziczona z klasyObject, która jest klasą bazową dla wszystkich klas w Javie. Każda klasa w Javie dziedziczy poObject, choć nie jest to jawnie widoczne.Klasa
Objectdostarcza kilka podstawowych metod, takich jakequals(),hashCode(),toString()i inne, które są używane do podstawowych operacji na obiektach.Nadpisanie metody
toString()pozwala na dostarczenie własnej implementacji, która będzie zwracać reprezentację obiektu w postaci ciągu znaków, co jest przydatne do debugowania i logowania.
Chociaż obie metody zwracają String, służą nieco innym celom:
info()to metoda, którą sami zdefiniowaliśmy na potrzeby naszej aplikacji.Jej celem jest dostarczenie ładnie sformatowanego, czytelnego dla użytkownika końcowego opisu obiektu
toString()to standardowa metoda z Javy, używana głównie do celów technicznych i deweloperskich. Jest automatycznie wywoływana podczas logowania, debugowania, a także, gdy próbujesz wydrukować obiekt bezpośrednio (np.System.out.println(ksiazka)).Dobrą praktyką jest nadpisywanie
toString(), aby dostarczała zwięzłej, deweloperskiej reprezentacji obiektu. W naszym projekcie często sprawiamy, że robi to samo coinfo()dla wygody, ale warto pamiętać o tym rozróżnieniu.
Po nadpisaniu metody toString() w klasie Creator, możemy ponownie przetestować nasze utwory muzyczne.
Tym razem, gdy wyświetlimy informacje o utworze muzycznym, lista wokalistów powinna być poprawnie sformatowana.
rów babicze - W GORĄCEJ WODZIE COMPANY (00:02:47) :: HIP_HOP
Szczegółowe informacje o W GORĄCEJ WODZIE COMPANY:
Tytuł: W GORĄCEJ WODZIE COMPANY
Autorzy: rów babicze
Czas trwania: 00:02:47
Gatunek: HIP_HOP
Wokaliści: [rów babicze]
Wulgarny: true
Język: Polski
Jeżeli wszystko działa poprawnie, to udało nam się dodać już trzeci rodzaj przedmiotu do naszego systemu bibliotecznego.
Skoro nasza biblioteka wypożycza nawet utwory muzyczne, to czemu nie miałaby wypożyczać filmów? Tym razem dodamy nowy rodzaj przedmiotu, który będzie reprezentować film lub inny materiał wideo.
Jak zawsze najpierw zastanówmy się, jakie informacje powinien zawierać taki film.
- Tytuł filmu — można skorzystać z pola
titlez interfejsuLibraryItem. - Czas trwania filmu — możemy skorzystać z interfejsu
Playable, który już mamy. - Reżyser filmu — moglibyśmy skorzystać z interfejsu
Authored, ale lepiej będzie stworzyć osobny interfejsDirected, który będzie zawierał specjalny obiektDirector, reprezentujący reżysera filmu.- Dodatkowo reżyser może przechowywać specjalnego Enuma
DirectorAward, który będzie odwzorowywał nagrody, jakie zdobył reżyser.
- Dodatkowo reżyser może przechowywać specjalnego Enuma
Wpierw stwórzmy Enum DirectorAward, który będzie reprezentować nagrody zdobywane przez reżyserów.
- W katalogu
itemstwórz enumDirectorAward. - Dodaj prywatne pole
fullNametypufinal String, które będzie przechowywać pełną nazwę nagrody. - Stwórz konstruktor, który przyjmuje pełną nazwę nagrody jako argument i inicjalizuje pole
fullName. - Dodaj publiczną metodę
getFullName(), która zwraca pełną nazwę nagrody. - Dodaj wartości:
OSCAR("Academy Award")GOLDEN_GLOBE("Golden Globe Award")BAFTA("British Academy Film Award")POLISH_FILM_AWARD("Polska Nagroda Filmowa (Orły)")
Teraz możemy stworzyć klasę Director, która będzie reprezentować reżysera filmu.
- W katalogu
itemstwórz klasęDirector. - Dodaj prywatne pole
awardstypuList<DirectorAward>, będzie to lista nagród zdobytych przez reżysera. - Dodaj publiczny konstruktor, który przyjmuje imię, nazwisko, pseudonim, datę urodzenia, datę śmierci (jeśli dotyczy) oraz listę nagród jako argumenty i inicjalizuje odpowiednie pola.
- Dodaj publiczną metodę
getAwards(), która zwraca listę nagród zdobytych przez reżysera.
Aktualnie w metodzie getAwards() zwracamy instancje listy nagród.
Dzięki temu możemy modyfikować tę listę poza klasą Director, co nie zawsze może być pożądane.
Aby zabezpieczyć się przed modyfikacją listy nagród, możemy zwrócić niemodyfikowalną kopię tej listy.
W ten sposób obiekt listy będzie niemodyfikowalny poza klasą Director.
Daje nam to również możliwość definiowania sposób modyfikacji listy nagród w klasie Director, np. poprzez osobne
metody addAward(DirectorAward award) i removeAward(DirectorAward award), które będą dodawać lub usuwać nagrody.
- Zrefaktoruj metodę
getAwards()w klasieDirector, aby zwracała kopię listy nagród:- Zamiast zwracać
awards, zwróćCollections.unmodifiableList(awards).
- Zamiast zwracać
- Dodaj metody
addAward(DirectorAward award)iremoveAward(DirectorAward award), które będą dodawać lub usuwać nagrody z wewnętrznej listyawards.
Aby umożliwić przedmiotom bibliotecznym posiadanie reżysera, stworzymy interfejs Directed,
który będzie definiował metody związane z reżyserem.
- W katalogu
itemstwórz interfejsDirected. - Dodaj metodę
getDirector(), która będzie zwracać reżysera (Director).
- W katalogu
itemstwórz podkatalogvideo. - W katalogu
videostwórz abstrakcyjną klasęVideo, która będzie implementować interfejsyLibraryItem,DirectediPlayable. - Dodaj prywatne pola:
titletypufinal String- tytuł filmu.durationtypufinal int- czas trwania filmu w sekundach.directortypufinal Director- reżyser filmu.
- Dodaj publiczny konstruktor, który przyjmuje tytuł, czas trwania i reżysera jako argumenty i inicjalizuje odpowiednie pola.
- Dodaj lub nadpisz publiczne metody:
getTitle()- zwraca tytuł filmu.getDuration()- zwraca czas trwania filmu w sekundach.getDirector()- zwraca reżysera filmu.
- Nadpisz metodę
info()z interfejsuLibraryItem, aby zwracała informacje o filmie w formacie:{title}, {director} ({duration})- Zamień
{title}na tytuł filmu, - Zamień
{director}na imię i nazwisko reżysera lub jego pseudonim, możesz do tego wykorzystać metodęgetFullName()z klasyCreator. - Zamień
{duration}na sformatowany czas trwania filmu w formacie(hh:mm:ss). Wspomóż się metodąformatDuration()z interfejsuPlayable.
- Zamień
- Nadpisz metodę
toString()w klasieVideo, aby zwracała to samo, co metodainfo().- Dzięki temu, gdy wyświetlimy obiekt
Videow konsoli, zobaczymy jego informacje w odpowiednim formacie.
- Dzięki temu, gdy wyświetlimy obiekt
Teraz możemy zabrać się za tworzenie konkretnych rodzajów filmów. Film fabularny to najpopularniejszy rodzaj filmu, więc zaczniemy od niego.
Najważniejszą częścią filmów fabularnych jest obsada aktorska.
Dlatego stworzymy pole actors, które będzie przechowywać listę aktorów występujących w filmie.
Jednak w tym przykładzie znacząco ulepszymy przechowywanie aktorów!
Zamiast przechowywać ich w liście List<String> lub List<Author> stworzymy dla nich osobną klasę Actor oraz
ActingRole, która będzie reprezentować rolę, jaką odgrywa aktor w filmie.
- W katalogu
videostwórz klasęActor, która będzie dziedziczyć po klasieCreator. - Dodaj prywatne pole
isVoiceActortypufinal booleanbędziemy mogli tego użyć, aby określić, czy aktor jest aktorem głosowym (np. w filmach animowanych). - Dodaj publiczny konstruktor, który przyjmuje imię, nazwisko, pseudonim, datę urodzenia, datę śmierci (jeśli dotyczy) oraz informację o tym, czy aktor jest aktorem głosowym jako argumenty i inicjalizuje odpowiednie pola.
- Dodaj publiczną metodę
isVoiceActor(), która zwraca informację, czy aktor jest aktorem głosowym.
- W katalogu
videostwórz klasęActingRole. - Dodaj prywatne pola:
nametypufinal String- nazwa roli, jaką odgrywa aktor w filmie.descriptiontypufinal String- opis roli, może być pusty, jeśli nie jest potrzebny.
- Dodaj publiczny konstruktor, który przyjmuje nazwę roli i opis jako argumenty i inicjalizuje odpowiednie pola.
- Dodaj publiczne metody:
getName()- zwraca nazwę roli.getDescription()- zwraca opis roli.
- Nadpisz metodę
toString(), aby zwracała nazwę roli i opis w formacie:{name} ({description})- Jeśli opis jest pusty, zamień
{description}nabrak opisu.
- Jeśli opis jest pusty, zamień
Po stworzeniu klas Actor i ActingRole, możemy w dogodny sposób przechowywać aktorów i ich role w filmie fabularnym.
Do przechowywania aktorów i ich ról użyjemy mapy Map<Actor, List<ActingRole>>, gdzie kluczem będzie aktor,
a wartością będzie lista ról, jakie odgrywa w filmie.
- W katalogu
videostwórz klasęMovie, która będzie dziedziczyć po klasieVideo. - Dodaj prywatne pole
actorstypuMap<Actor, List<ActingRole>>, które będzie przechowywać aktorów i ich role. - Dodaj publiczny konstruktor, który przyjmuje tytuł, czas trwania, reżysera i mapę aktorów jako argumenty
i inicjalizuje odpowiednie pola.
- Jeśli mapa aktorów jest nullem, zainicjalizuj ją jako pustą mapę.
- Dodaj publiczne metody:
-
addActor(Actor actor, ActingRole... roles)- dodaje aktora do filmu wraz z jego rolami.- Jeśli
actorlubrolesjest nullem, rzuć wyjątekIllegalArgumentException.- Dodaj wiadomość do wyjątku, że rola oraz aktor nie może być nullem.
- Jeśli
rolesjest pustą tablicą, rzuć wyjątekIllegalArgumentException.- Dodaj wiadomość do wyjątku, że aktor musi mieć co najmniej jedną rolę.
- Jeśli aktor już występuje w filmie, dodaj nowe role do istniejącej listy ról.
- Możesz skorzystać z metody
computeIfAbsent()z klasyMap, aby utworzyć nową listę ról, jeśli aktor nie istnieje.
- Jeśli
-
getActors()- zwraca niemodyfikowalne zestawienie aktorów (Set<Actor>) występujących w filmie.- Możesz skorzystać z metody
keySet()z klasyMap.
- Możesz skorzystać z metody
-
getRolesForActor(Actor actor)- zwraca niemodyfikowalną listę ról (List<ActingRole>) dla danego aktora.Jeżeli aktor nie występuje w filmie, zwróć pustą listę.
- Możesz skorzystać z metody
getOrDefault()z klasyMap, aby uzyskać listę ról dla danego aktora lub zwrócić pustą listę, jeśli aktor nie występuje w filmie.
- Możesz skorzystać z metody
-
removeActor(Actor actor)- usuwa aktora z filmu.- Jeśli
actorjest nullem, rzuć wyjątekIllegalArgumentException. - Możesz skorzystać z metody
remove()z klasyMap, aby usunąć aktora i jego role.
- Jeśli
-
removeRoleForActor(Actor actor, ActingRole role)- usuwa konkretną rolę dla danego aktora.- Jeśli
actorlubrolejest nullem, rzuć wyjątekIllegalArgumentException. - Sprawdź, czy aktor występuje w filmie, a następnie usuń rolę z listy ról tego aktora.
- Jeśli lista ról stanie się pusta po usunięciu roli, usuń aktora z mapy.
- Jeśli
-
- Nadpisz metodę
info()z klasyVideo, aby zwracała informacje o filmie w formacie:{title}, {director} ({duration}) :: {actors}, gdzie{actors}to lista aktorów i ich ról oddzielonych przecinkiem w formacie:{actor} ({roles}), gdzie{actor}to pełne imię i nazwisko lub pseudonim aktora, a{roles}to lista ról oddzielona przecinkami.- Skorzystaj z metody
getFullName()z klasyCreatororaz metodytoString()z klasyActingRole.
W taki sposób stworzyliśmy klasę Movie, która przechowuje aktorów i ich role w wygodny i bezpieczny sposób.
Mapto struktura danych, która przechowuje pary klucz-wartość. W naszym przypadku kluczem jest obiektActor, a wartością lista jego ról (List<ActingRole>).Dzięki temu możemy łatwo uzyskać dostęp do ról dla danego aktora, a także sprawdzić, czy dany aktor występuje w filmie.
Dodatkowo,
Mappozwala na szybkie wyszukiwanie aktorów i ich ról, co jest przydatne w przypadku filmów z dużą obsadą.
- W katalogu
projectotwórz plikMain.java. - Stwórz przykładowego reżysera:
Director nolan = new Director("Christopher", "Nolan", null, LocalDate.of(1970, 7, 30), null, List.of(DirectorAward.OSCAR, DirectorAward.GOLDEN_GLOBE));
- Stwórz przykładowych aktorów:
Actor dicaprio = new Actor("Leonardo", "DiCaprio", null, LocalDate.of(1974, 11, 11), null, false); Actor gordonLevitt = new Actor("Joseph", "Gordon-Levitt", null, LocalDate.of(1981, 2, 17), null, false);
- Stwórz przykładowe role:
ActingRole cobbRole = new ActingRole("Cobb", "Główny bohater, specjalista od ekstrakcji snów."); ActingRole arthurRole = new ActingRole("Arthur", "Partner Cobba, odpowiedzialny za research.");
- Stwórz instancje
Movie:Movie inception = new Movie("Incepcja", 8880, nolan, null);
- Dodaj aktorów i ich role do filmu:
inception.addActor(dicaprio, cobbRole); inception.addActor(gordonLevitt, arthurRole);
- Dodaj film do wypożyczonych przedmiotów użytkownika:
janKowalski.addItem(inception);
- Dodaj wyświetlanie informacji o filmie:
System.out.println("\n--- TESTOWANIE FILMU ---"); System.out.println(inception.info()); System.out.println(janKowalski.info());
- Dodaj dodawanie i usuwanie ról aktorów:
// Dodajemy nowego aktora z jego rolą System.out.println("\n...Dodawanie nowego aktora..."); Actor hardy = new Actor("Tom", "Hardy", null, LocalDate.of(1977, 9, 15), null, false); ActingRole eamesRole = new ActingRole("Eames", "Fałszerz, potrafi zmieniać wygląd w świecie snów."); inception.addActor(hardy, eamesRole); System.out.println(inception.info()); // Usuwamy jedną z ról System.out.println("\n...Usuwanie roli aktora..."); inception.removeRoleForActor(gordonLevitt, arthurRole); System.out.println(inception.info()); // Po usunięciu ostatniej roli aktor powinien zniknąć z obsady System.out.println("Czy Joseph Gordon-Levitt nadal jest w obsadzie? " + inception.getActors().contains(gordonLevitt));
Po uruchomieniu programu powinieneś zobaczyć informacje o filmie, aktorach i ich rolach w odpowiednim formacie tak jak poniżej:
--- TESTOWANIE FILMU ---
Incepcja, Christopher Nolan (02:28:00) :: Joseph Gordon-Levitt (Arthur (Partner Cobba, odpowiedzialny za research.)), Leonardo DiCaprio (Cobb (Główny bohater, specjalista od ekstrakcji snów.))
Jan Kowalski (1): 4 wypożyczone przedmioty
...Dodawanie nowego aktora...
Incepcja, Christopher Nolan (02:28:00) :: Joseph Gordon-Levitt (Arthur (Partner Cobba, odpowiedzialny za research.)), Tom Hardy (Eames (Fałszerz, potrafi zmieniać wygląd w świecie snów.)), Leonardo DiCaprio (Cobb (Główny bohater, specjalista od ekstrakcji snów.))
...Usuwanie roli aktora...
Incepcja, Christopher Nolan (02:28:00) :: Tom Hardy (Eames (Fałszerz, potrafi zmieniać wygląd w świecie snów.)), Leonardo DiCaprio (Cobb (Główny bohater, specjalista od ekstrakcji snów.))
Czy Joseph Gordon-Levitt nadal jest w obsadzie? false
Kolejnym rodzajem filmu, który możemy dodać, jest film dokumentalny.
Wykorzystamy w nim klasę Actor, ale nie będziemy przechowywać ról aktorów.
-
W katalogu
videostwórz klasęDocumentary, która będzie dziedziczyć po klasieVideo. -
Dodaj prywatne pola:
subjecttypufinal String- temat dokumentunarratortypufinal Actor- narrator filmu. To pole może byćnull, jeśli dokument nie ma narratora lub jest nieznany.
-
Dodaj główny, publiczny konstruktor, który przyjmuje wszystkie argumenty, w tym narratora:
public Documentary(String title, int duration, Director director, String subject, Actor narrator)- Wewnątrz konstruktora sprawdź, czy przekazany narrator nie jest
null. Jeśli nie jest, upewnij się, że jest on aktorem głosowym (jego metodaisVoiceActor()zwraca true). Jeśli warunek nie jest spełniony, rzuć wyjątekIllegalArgumentExceptionz odpowiednim komunikatem, np.Narrator musi być aktorem głosowym!.
- Wewnątrz konstruktora sprawdź, czy przekazany narrator nie jest
-
Stwórz drugi, przeciążony konstruktor. Będzie on służył do wygodnego tworzenia filmów dokumentalnych, które nie mają narratora lub nie jest on znany.
- Konstruktor ten powinien przyjmować tylko
title,duration,directorisubject. - Jego jedynym zadaniem powinno być wywołanie głównego konstruktora za pomocą słowa kluczowego
this(), przekazując munullw miejsce narratora.
- Konstruktor ten powinien przyjmować tylko
-
Dodaj publiczne metody:
getSubject()- zwraca temat dokumentu.getNarrator()- zwraca narratora dokumentu.- Metoda powinna zwracać typ
Optional<Actor>, a nieActor. UżyjOptional.ofNullable(narrator), aby opakować potencjalnie pustą wartość.
- Metoda powinna zwracać typ
-
Nadpisz metodę
info(), aby wyświetlała szczegóły dokumentu. Wykorzystaj metody zOptional, aby bezpiecznie dodać informację o narratorze tylko wtedy, gdy on istnieje w formacie:{title}, {director} ({duration}) :: {subject} (Narracja: {narrator})- Podczas formatowania sprawdź, czy narrator jest obecny korzystając z metod dostępnych w
Optional. Jeśli tak, dodaj(Narracja: {narrator}), gdzie{narrator}to pełne imię i nazwisko lub pseudonim narratora.
- Podczas formatowania sprawdź, czy narrator jest obecny korzystając z metod dostępnych w
-
Nadpisz metodę
toString(), aby zwracała to samo, co metodainfo().
this(...)to specjalna konstrukcja w Javie, która pozwala na wywołanie innego konstruktora w tej samej klasie.Najczęściej wykorzystuje się ją do unikania duplikacji kodu w konstruktorach, w naszym przypadku aby wywołać główny konstruktor z innymi argumentami.
Optionalto specjalny typ w Javie, który reprezentuje wartość, która może być obecna lub nieobecna.Jego głównym celem jest uniknięcie błędów typu
NullPointerException. Zamiast zwracaćnull, co jest ryzykowne, zwracamyOptional. Programista, który wywołuje metodęgetNarrator(), jest wtedy zmuszony "otworzyć pudełko" i świadomie obsłużyć przypadek, gdy jest ono puste. To sprawia, że kod jest znacznie bezpieczniejszy i bardziej czytelny.Oprócz tego,
Optionaldostarcza wiele przydatnych metod do pracy z wartościami, które mogą być puste, np.isPresent(),ifPresent(),orElse(),map()i inne. Dzięki nim możemy łatwo sprawdzić, czy wartość jest obecna, i wykonać odpowiednie operacje.
- Stwórz przykładowego reżysera oraz narratora filmu:
Director herzog = new Director("Werner", "Herzog", null, LocalDate.of(1942, 9, 5), null, List.of()); Actor narrator = new Actor("Krystyna", "Czubówna", null, LocalDate.of(1954, 8, 2), null, true);
- Stwórz dla obiekty
Documentary, jeden z nich będzie zawierał narratora a drugi nie:// Dokument z narratorem - używamy głównego konstruktora Documentary lifeOnEarth = new Documentary("Życie na Ziemi", 5400, herzog, "Przyroda", narrator); // Dokument bez narratora - używamy przeciążonego konstruktora Documentary industrialRevo = new Documentary("Rewolucja Przemysłowa", 3600, herzog, "Historia");
- Dodaj oba dokumenty do wypożyczonych przedmiotów użytkownika:
adamNowak.addItem(lifeOnEarth); adamNowak.addItem(industrialRevo);
- Dodaj wyświetlanie informacji o dokumentach:
System.out.println("\n--- TESTOWANIE FILMU DOKUMENTALNEGO ---"); System.out.println(lifeOnEarth.info()); System.out.println(industrialRevo.info());
- Na koniec przetestuj działanie
OptionalzgetNarrator(). Użyjemy metody ifPresent(), która wykonuje podaną akcję tylko wtedy, gdy Optional nie jest pusty:System.out.println("\nSprawdzanie narratora w '" + lifeOnEarth.getTitle() + "':"); lifeOnEarth.getNarrator().ifPresent(n -> { System.out.println(" Narratorem jest: " + n.getFullName()); }); System.out.println("\nSprawdzanie narratora w '" + industrialRevo.getTitle() + "':"); industrialRevo.getNarrator().ifPresent(n -> { // Ten tekst nie powinien się wyświetlić System.out.println(" Narratorem jest: " + n.getFullName()); }); // Możemy też sprawdzić inaczej if (industrialRevo.getNarrator().isEmpty()) { System.out.println(" Ten dokument nie ma narratora."); }
Po uruchomieniu programu powinieneś zobaczyć informacje o dokumentach, narracji i sprawdzeniu narratora w odpowiednim formacie:
--- TESTOWANIE FILMU DOKUMENTALNEGO ---
Życie na Ziemi, Werner Herzog (01:30:00) :: Przyroda (Narracja: Krystyna Czubówna)
Rewolucja Przemysłowa, Werner Herzog (01:00:00) :: Historia
Sprawdzanie narratora w 'Życie na Ziemi':
Narratorem jest: Krystyna Czubówna
Sprawdzanie narratora w 'Rewolucja Przemysłowa':
Ten dokument nie ma narratora.
Jako ostatni rodzaj filmu, jaki dodamy do naszej biblioteki, będzie anime.
- W katalogu
videostwórz podkataloganime. - W katalogu
animestwórz enumAnimeType, który będzie reprezentować typy anime:TV_SERIES("TV Series")MOVIE("Movie")OVA("OVA")ONA("ONA")SPECIAL("Special")
- W katalogu
animestwórz klasęCharacter.- Zmień typ klasy na
record, aby uprościć definicję. Aby to zrobić, zmień deklarację klasy na:public record Character(...) { }
- Wewnątrz rekordu dodaj pola:
nametypuString- będzie przechowywać nazwę postaci.voiceActortypuActor- będzie przechowywać aktora głosowego, który podkłada głos pod tę postać.isMainCharactertypuboolean- będzie przechowywać informację, czy postać jest główną postacią w anime.
public record Character( String name, Actor voiceActor, boolean isMainCharacter ) { }
- Zmień typ klasy na
- W katalogu
animestwórz klasęAnime, która będzie dziedziczyć po klasieVideo.- Dodaj prywatne pola:
originalTitletypufinal String- japoński tytuł anime.typetypufinal AnimeType- typ anime.characterstypufinal Set<Character>- Zbiór postaci w anime.
- Dodaj publiczny konstruktor, który przyjmuje wszystkie argumenty i inicjalizuje odpowiednie pola.
- Dodaj publiczne metody:
getOriginalTitle()- zwraca japoński tytuł anime.getType()- zwraca typ anime.getCharacters()- zwraca listę postaci w anime.
- Nadpisz metodę
info(), aby wyświetlała szczegóły anime w formacie:{japaneseTitle} ({englishTitle}) {type} ({duration}) - Nadpisz metodę
toString(), aby zwracała to samo, co metodainfo().
- Dodaj prywatne pola:
- Stwórz najpierw aktorów głosowych dla postaci:
Actor kaji = new Actor("Yūki", "Kaji", null, LocalDate.of(1985, 9, 3), null, true); Actor ishikawa = new Actor("Yui", "Ishikawa", null, LocalDate.of(1989, 5, 30), null, true); Actor murakawa = new Actor("Rie", "Murakawa", null, LocalDate.of(1990, 1, 1), null, true); Actor minase = new Actor("Inori", "Minase", null, LocalDate.of(1995, 12, 2), null, true);
- Teraz możemy stworzyć obiekty postaci:
Character eren = new Character("Eren Yeager", kaji, true); Character mikasa = new Character("Mikasa Ackerman", ishikawa, true); Character ram = new Character("Ram", murakawa, true); Character rem = new Character("Rem", minase, true);
- Następnie reżysera/twórcę anime:
Director araki = new Director("Tetsurō", "Araki", null, LocalDate.of(1976, 11, 5), null, List.of()); Director watanabe = new Director("Masaharu", "Watanabe", null, null, null, List.of());
- A na koniec instancje
Anime:Anime attackOnTitan = new Anime("Attack on Titan", 1440, araki, "進撃の巨人 (Shingeki no Kyojin)", AnimeType.TV_SERIES, Set.of(eren, mikasa)); Anime reZero = new Anime("Re:ZERO - Starting Life in Another World", 1500, watanabe, "Re:ゼロから始める異世界生活", AnimeType.TV_SERIES, Set.of(ram, rem));
- Dodaj oba anime do wypożyczonych przedmiotów użytkownika:
janKowalski.addItem(attackOnTitan); janKowalski.addItem(reZero);
- Dodaj wyświetlanie informacji o anime:
System.out.println("\n--- TESTOWANIE ANIME ---"); System.out.println(attackOnTitan.info()); System.out.println(reZero.info());
Po uruchomieniu programu powinieneś zobaczyć informacje o anime w odpowiednim formacie, np.:
--- TESTOWANIE ANIME ---
進撃の巨人 (Shingeki no Kyojin) (Attack on Titan), TV (00:24:00)
Re:ゼロから始める異世界生活 (Re:ZERO - Starting Life in Another World), TV (00:25:00)
W ten sposób pomyślnie przetestowaliśmy ostatni element naszej biblioteki.
I to by było na tyle na tym etapie!
Jeśli wykonałeś/wykonałaś wszystkie kroki, to właśnie udało Ci się zbudować od zera szkielet złożonej, ale elastycznej aplikacji w Javie.
To nie jest już prosty programik, a system z przemyślaną architekturą, który można łatwo rozbudowywać o nowe funkcje i typy danych. Zobaczyliśmy, jak z małych, prostych klas można budować coraz większe i bardziej skomplikowane struktury, które współgrają ze sobą.
- Abstrakcja i Enkapsulacja: Tworzyłeś/tworzyłaś klasy abstrakcyjne (
Person,Book,Video) i interfejsy (LibraryItem,Playable,Authored), ukrywając szczegóły implementacji za pomocą prywatnych pól. - Dziedziczenie: Rozbudowywałeś klasy bazowe, tworząc bardziej wyspecjalizowane wersje,
np.
MovieiDocumentarydziedziczące poVideo. - Polimorfizm: Przechowywałeś w jednej liście (
List<LibraryItem>) obiekty różnych typów (książki, filmy, muzykę), co jest sercem elastycznego projektowania.
- Kolekcje: Używałeś fundamentalnych struktur danych jak
List,SetiMap, ucząc się, kiedy i dlaczego wybrać każdą z nich. - Typy Wyliczeniowe (enum): Stworzyłeś bezpieczne i czytelne typy dla gatunków muzycznych czy nagród reżyserskich, zamiast używać zwykłych Stringów.
- Słowa kluczowe: W praktyce zobaczyłeś, jak działają
final,super,this,defaulti@Override. - Obsługa błędów: Nauczyłeś się rzucać wyjątki (
IllegalArgumentException), aby Twój kod był bardziej odporny na błędy i łatwiejszy do debugowania.
- Refaktoryzacja: Ulepszyłeś istniejący kod (np. zmieniając pole author na listę autorów), co jest jedną z najważniejszych umiejętności praktycznych podczas pracy z kodem.
- Optional: Nauczyłeś się, jak bezpiecznie obsługiwać wartości, które mogą być
null, unikając najczęstszego błędu w Javie, jakim jestNullPointerException. - Rekordy: Poznałeś nowoczesny sposób na tworzenie zwięzłych, niezmiennych klas do przechowywania danych (
Character). - Programowanie defensywne: Zabezpieczałeś listy przed modyfikacją (
Collections.unmodifiableList) i walidowałeś dane w konstruktorach.
Dzięki tym wszystkim umiejętnościom jesteś teraz w stanie tworzyć bardziej złożone aplikacje w Javie, które są nie tylko funkcjonalne, ale także łatwe do rozbudowy i utrzymania.
W tej części projektu zajmiemy się zarządzaniem biblioteką, czyli tworzeniem systemu, który pozwoli na wypożyczanie i zwracanie przedmiotów, a także na zarządzanie przedmiotami i użytkownikami biblioteki.
TBA



