REWIND #1: Classes
We reached a milestone!
The past few lessons were a lot to get through!
But, we’ve also learnt a lot!
With these new concepts, we can do lots of really cool things! This is what the REWIND lessons will be about.
We are going to combine lots of concepts together, and make one really cool application.
Get creative and feel free to add more functionality to the system below!
Introducing Books ‘r’ Us
We have been approached by Books ‘r’ Us, which is a local business selling books.
As they are expanding their business, they need a better way to manage things—especially as they are considering to go online!
The required system functionality
We need to develop a system that with the following functionality:
- Maintain the book seller’s cash balance.
- When the book seller purchases a new book:
- The cash balance is reduced (by the cost price).
- The book is added to the collection of books on offer.
- Give each book a unique ID.
- A book can have its selling price reduced.
- When the book seller sells a book:
- The book is sold by scanning the book’s unique ID.
- The cash balance is increased (by the sale price).
- The book is marked as sold in the collections of books on offer.
- Given a unique ID, grab the book that corresponds to that ID (if any).
- Given a keyword in a book’s title, search and grab a book that matches the keyword (if any).
- Determine the total number of sold and unsold books (include duplicate books).
- Determine the total number of unsold books (include duplicate books).
- Given a book, sell all copies of that book (equality based on title, author, publication year).
We also notice there is the possibility for our system to cater for multiple book sellers.
Designing the entities
Before we start coding, we need to do some design work.
As we saw in the OOP Thought Process lesson, we think about what we need to model and represent:
What are the entities (“things”) in our model?
- Books, and
- Book sellers.
What properties/characteristics do those entities have?
- A book has:
- a title,
- an author,
- a year,
- a cost price,
- a sell price,
- a status as to whether it is sold or not, and
- a unique ID.
- A book seller has:
- a shop name,
- a cash balance, and
- a collection of books.
What actions can those entities do, or can we perform on them?
- A book can:
- tell us some of its attributes (getters), and possibility allow us to change some attributes (setters),
- have its selling price discounted.
- A book seller can:
- tell us some of its attributes (getters), and possibility allow us to change some attributes (setters),
- purchase book stock,
- sell a particular copy of a book,
- sell all copies of a book,
- search for a book based on a keyword, etc.
What are the relationships between those entities?
We will dig more into relationships later, but these are already implied in the above properties and actions.
Example usage
This is how we might make use of our system:
public class Main {
public static void main(String[] args) {
// Create BookSeller instance and print details
BookSeller shop = new BookSeller("Books 'r' Us", 100.0);
System.out.println("Starting balance: $" + shop.getCashBalance());
System.out.println();
// Create a few Book instances
Book hp1 = new Book("Harry Potter Philosopher Stone", "J. K. Rowling", 1997, 10.50, 29.95); // ID: 0
Book hp2a = new Book("Harry Potter Chamber Secrets", "J. K. Rowling", 1998, 11.50, 31.95); // ID: 1
Book hp2b = new Book("Harry Potter Chamber Secrets", "J. K. Rowling", 1998, 9.50, 31.95); // ID: 2
Book phil = new Book("Philosophy 101", "Paul Kleinman", 2013, 8.75, 15.99); // ID: 3
// BookSeller purchases one of the Books as stock
shop.purchaseStock(hp2b);
System.out.println("Balance after purchasing stock: $" + shop.getCashBalance());
System.out.println("Total books: " + shop.getTotalNumberOfBooks());
System.out.println();
// BookSeller purchases more Books as stock
shop.purchaseStock(hp1);
shop.purchaseStock(hp2a);
shop.purchaseStock(phil);
System.out.println("Balance after purchasing more stock: $" + shop.getCashBalance());
System.out.println("Total books: " + shop.getTotalNumberOfBooks());
System.out.println();
// Scan a barcode to find a Book
Book result = shop.scanByID(3);
if (result != null) {
System.out.println("Search by ID found: " + result);
} else {
System.out.println("Search by ID didn't find anything.");
}
// Reduce the selling prices of these books
phil.reduceSellPrice(1.1);
hp2a.reduceSellPrice(50);
// Try to sell a Book
boolean success = shop.sellBook(3);
if (success) {
System.out.println("Sold book: " + result);
} else {
System.out.println("Could not sell: " + result);
}
System.out.println();
// Search for a Book that contains the specified keyword
result = shop.searchKeyword("SeCReTs");
if (result != null) {
System.out.println("Search by keyword found: " + result);
int num = shop.sellAllCopiesOfBook(result);
System.out.println("Sold " + num + " copies.");
} else {
System.out.println("Could not find something with that keyword." );
}
System.out.println("At the end of the day, balance is $" + shop.getCashBalance());
}
}
Let’s get busy!
You are provide with a blank project:
Get your hands dirty!
Edit the above code:
- Define a
BookSeller
class inside aBookSeller.java
file.- Declare and define a
BookSeller
constructorpublic BookSeller(String shopName, double initialBalance)
. - Declare and define a getter method
public double getCashBalance()
. - Inside the
main()
method, create an instance ofBookSeller
and print its starting cash balance as in the Example Usage section above.
- Declare and define a
- Define a
Book
class inside aBook.java
file.- Implement a
Book
constructor that expects:- a title,
- an author,
- a year,
- a cost price, and
- a sell price.
- Assign a unique ID to the book when it is constructed, using a
static
variable.
- Implement a
- Create a few
Book
instances, as in the example above.- Add a
getId()
method for theBook
class. - Test that each book instance has a unique ID returned.
- Add a
- Override the
public String toString()
method for theBook
class.- Test it by calling it on the
Book
instances you made above.
- Test it by calling it on the
- Add a
public void purchaseStock(Book book)
inBookSeller
.- Decrement the book seller’s cash balance (will need to add a
getCostPrice()
instance method forBook
). - Store the book inside an
ArrayList
. Hints: InsideBookSeller
, you will need to:import java.util.ArrayList;
,- Declare a
private ArrayList<Book> collection
field, and - Make use of the
add()
method on theArrayList
.
- Decrement the book seller’s cash balance (will need to add a
-
Implement a method
public int getTotalNumberOfBooks()
insideBookSeller
. - Try to purchase the same
Book
instance multiple times. What happens to the cash balance, and total number of books? Does it make sense that attempting to purchase the exact-sameBook
instance again and again causes the balance and number of books to change? Absolutely not! ABook
instance represents a single (physical) book!- Fix the
purchaseStock(Book book)
method, so that it checks to see if thebook
is already in the collection. - Hint: We want this to operate at the identity level, not equality level. This means you should not rely on the
contains()
method of theArrayList
class, as that operates at the equality level.
- Fix the
- Implement a method
public Book scanByID(int id)
insideBookSeller
.- Loop through each book in the
collection
. If a book’s ID matches the id we want, return the book. - If a book with that id wasn’t found, return
null
. - Test it by retrieving existing/missing books in the collection:
- Print its details out (either use its
toString()
or add agetTitle()
method). - Be sure to safe-guard this printing with an if-statement, just in case
null
was returned.
- Print its details out (either use its
- Loop through each book in the
- Implement a method
public boolean sellBook(int id)
insideBookSeller
.- Reuse the
scanByID()
method to find theBook
. - If no book was found in the scan, then return
false
fromsellBook()
. - Add a
sold
status toBook
, with its respective setter (public void sell()
) and getter (public boolean isSold()
).- You will also need to initialise this variable in the
Book
’s constructor.
- You will also need to initialise this variable in the
- For the retrieved book, check it isn’t already sold.
- If it is already sold, return
false
fromsellBook()
. - Otherwise, update its status to sold, update the
BookSeller
’s cash balance, and returntrue
fromsellBook()
.
- If it is already sold, return
- Reuse the
- Implement a method
public void printInventoryDetails()
insideBookSeller
. It should print the details of all books that were ever part of the inventory. Here’s an example output:Inventory details for Books 'r' Us: 1: ***SOLD*** [BOOK:title=Harry Potter Chamber Secrets, id=2] 2: AVAILABLE [BOOK:title=Harry Potter Philosopher Stone, id=0] 3: ***SOLD*** [BOOK:title=Philosophy 101, id=3] 4: AVAILABLE [BOOK:title=Harry Potter Chamber Secrets, id=1]
- Hints:
- The leading numbers represent the book’s position within the collection, starting from
1
. This is not the same as its ID. So, you should not need to edit theBook
class. - Use the
Book
’s naturaltoString()
to help. - Use the
Book
’sisSold()
getter to help.
- The leading numbers represent the book’s position within the collection, starting from
- Hints:
- Implement a method
public void reduceSellPrice(double deltaAmount)
insideBook
:- It reduces the sell price of a particular book instance by (at most) the specified
deltaAmount
. - However, the book seller never wants to sell a book below cost! So you need to make sure the sell price never drops below its cost price!
- If the
deltaAmount
causes the sell price to drop below the cost price, then make the sell price equal to the cost price. - Test it out with a few different deltas on the books we have already.
- Notice: Hopefully this makes you appreciate why we would want fields to be
private
! It allows us to safeguard the state of our instance (in this case, to ensure that the sell price doesn’t change to something too low!).
- It reduces the sell price of a particular book instance by (at most) the specified
- Implement a method
public int getTotalNumberOfUnsoldBooks()
insideBookSeller
.- Loop through all books and count how many books aren’t sold yet. Return that count.
- Implement a method
public Book searchKeyword(String keyword)
insideBookSeller
.- Loop through the collection. If the keyword is in the title, return the book. Otherwise return
null
. - Make the search case insensitive.
- Loop through the collection. If the keyword is in the title, return the book. Otherwise return
- Implement a method
public int sellAllCopiesOfBook(Book bookToSell)
insideBookSeller
.- Define equality for
Book
by overriding thepublic boolean equals(Object obj)
method.- Equality of books depends on the title, author, and year.
- Strictly speaking, you should also override the
public int hashCode()
method.
- Inside
sellAllCopiesOfBook()
, define acount
for the number of copies (initially set to zero). - For each “equal book” in the collection, attempt to sell it using the existing
sellBook()
method.- Check if it was successful (according to the returned value
sellBook()
). If so, increment the count.
- Check if it was successful (according to the returned value
- Finally, return the count.
- Define equality for
- Implement a method
public static int getOldestBookYear()
insideBook
. It will return the year of the oldest book that exists. You will need to also make changes to yourBook
constructor (e.g. to figure out if the book instance constructed is older than the current oldest book).