Edit

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

Edit

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?

  1. Books, and
  2. Book sellers.

What properties/characteristics do those entities have?

  1. 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.
  2. 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?

  1. A book can:
    • tell us some of its attributes (getters), and possibility allow us to change some attributes (setters),
    • have its selling price discounted.
  2. 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:


Edit Get your hands dirty!

Edit the above code:

  1. Define a BookSeller class inside a BookSeller.java file.
    • Declare and define a BookSeller constructor public BookSeller(String shopName, double initialBalance).
    • Declare and define a getter method public double getCashBalance().
    • Inside the main() method, create an instance of BookSeller and print its starting cash balance as in the Example Usage section above.
  2. Define a Book class inside a Book.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.
  3. Create a few Book instances, as in the example above.
    • Add a getId() method for the Book class.
    • Test that each book instance has a unique ID returned.
  4. Override the public String toString() method for the Book class.
    • Test it by calling it on the Book instances you made above.
  5. Add a public void purchaseStock(Book book) in BookSeller.
    • Decrement the book seller’s cash balance (will need to add a getCostPrice() instance method for Book).
    • Store the book inside an ArrayList. Hints: Inside BookSeller, you will need to:
      • import java.util.ArrayList;,
      • Declare a private ArrayList<Book> collection field, and
      • Make use of the add() method on the ArrayList.
  6. Implement a method public int getTotalNumberOfBooks() inside BookSeller.

  7. 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-same Book instance again and again causes the balance and number of books to change? Absolutely not! A Book instance represents a single (physical) book!
    • Fix the purchaseStock(Book book) method, so that it checks to see if the book 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 the ArrayList class, as that operates at the equality level.
  8. Implement a method public Book scanByID(int id) inside BookSeller.
    • 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 a getTitle() method).
      • Be sure to safe-guard this printing with an if-statement, just in case null was returned.
  9. Implement a method public boolean sellBook(int id) inside BookSeller.
    • Reuse the scanByID() method to find the Book.
    • If no book was found in the scan, then return false from sellBook().
    • Add a sold status to Book, 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.
    • For the retrieved book, check it isn’t already sold.
      • If it is already sold, return false from sellBook().
      • Otherwise, update its status to sold, update the BookSeller’s cash balance, and return true from sellBook().
  10. Implement a method public void printInventoryDetails() inside BookSeller. 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 the Book class.
      • Use the Book’s natural toString() to help.
      • Use the Book’s isSold() getter to help.
  11. Implement a method public void reduceSellPrice(double deltaAmount) inside Book:
    • 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!).
  12. Implement a method public int getTotalNumberOfUnsoldBooks() inside BookSeller.
    • Loop through all books and count how many books aren’t sold yet. Return that count.
  13. Implement a method public Book searchKeyword(String keyword) inside BookSeller.
    • Loop through the collection. If the keyword is in the title, return the book. Otherwise return null.
    • Make the search case insensitive.
  14. Implement a method public int sellAllCopiesOfBook(Book bookToSell) inside BookSeller.
    • Define equality for Book by overriding the public 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 a count 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.
    • Finally, return the count.
  15. Implement a method public static int getOldestBookYear() inside Book. It will return the year of the oldest book that exists. You will need to also make changes to your Book constructor (e.g. to figure out if the book instance constructed is older than the current oldest book).