Description:
Design a book library which maintains the book inventory. The library should allow an user to favorite/un-favorite book. The library should allow an user to rent a book for a given period of time, no other user can rent the same book for overlapping period. There can be multiple copies of a book.
At present the code looks mess to me because all the logic is cramped into a single class, this is intentional and I would like to learn how to the decompose classes like these because sometimes we may need to work on some legacy code.
Code:
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
import java.util.UUID;
import java.util.Date;
class LibraryService {
private final Map<String, Integer> bookQuantity;
private final Map<String, Book> books;
private final Map<User, BookRent> rentedBooks;
private final Map<User, Set<Book>> favorites;
LibraryService() {
books = new HashMap<>();
favorites = new HashMap<>();
bookQuantity = new HashMap<>();
rentedBooks = new HashMap<>();
}
void addBook(Book book, int quantity) {
assert quantity > 0;
String title = book.getTitle();
books.put(title, book);
bookQuantity.put(title, quantity);
}
void removeBook(Book book) throws Exception {
// pre-condition
for (Map.Entry<User, Set<Book>> pair : favorites.entrySet()) {
removeFavorite(pair.getKey(), book);
}
String title = book.getTitle();
books.remove(title);
bookQuantity.remove(title);
}
void rent(User user, String title, Date start, Date end) throws Exception {
assert start.before(end);
Book found = findByTitle(title);
if (found == null) {
throw new Exception("Book not found");
}
decreaseQuantity(found);
}
void returnBook(User user, String title) throws Exception {
Book found = findByTitle(title);
if (found == null) {
throw new Exception("Book not found");
}
increaseQuantity(found, 1);
}
Book findByTitle(String title) {
return books.get(title);
}
public void favorite(User user, Book book) throws Exception {
// the book should be in stock
Book found = findByTitle(book.getTitle());
if (found == null) {
throw new Exception("Book not found");
}
if (favorites.get(user) == null) {
favorites.put(user, new HashSet<Book>() {{
add(book);
}});
} else {
favorites.get(user).add(book);
}
}
public Set<Book> getFavorites(User user) {
return favorites.get(user);
}
public boolean removeFavorite(User user, Book book) {
return favorites.get(user).remove(book);
}
private void increaseQuantity(Book book, int quantity) {
String title = book.getTitle();
Integer prevQuantity = bookQuantity.get(title);
if (prevQuantity == null) {
prevQuantity = 0;
}
bookQuantity.put(title, prevQuantity + quantity);
}
private void decreaseQuantity(Book book) throws Exception {
String title = book.getTitle();
Integer prevQuantity = bookQuantity.get(title);
if (prevQuantity == null || prevQuantity == 0) {
throw new Exception("Book out of stock");
}
bookQuantity.put(title, prevQuantity - 1);
}
}
final class Book {
private final UUID uid;
private final String title;
private final String author;
Book(String title, String author) {
this.title = title;
this.author = author;
uid = UUID.randomUUID();
}
String getTitle() {
return title;
}
String getAuthor() {
return author;
}
public String toString() {
return "Book: " + this.getTitle() +
" (" + this.getAuthor() + ")";
}
}
class User {
private final UUID uid;
private final String name;
User(String name) {
this.name = name;
uid = UUID.randomUUID();
}
}
Questions:
- Where can I have custom exception classes?
- Where to catch exception vs throw them?
- When and where I can have assert statement?
- When can I pass book instance vs book title?
PS: Review should be done keeping in mind that the code is in production and used by thousand of users.
Note:
I am thinking to implement the feature to include date range while renting a book so, when renting a book the dates should not overlap. This feature is not in the scope but would be great if anyone can suggest me something.