Building a GUI for a Bookstore Application
A JavaFX bookstore app with a points-based rewards system, built around three real design patterns: Singleton, Observer, and State.
Why it exists
Built as the culmination of COE528 - Object-Oriented Engineering Analysis and Design, this wasn't really about building a bookstore. It was about proving three classic design patterns could be applied correctly inside one working app, backed by UML class/use-case diagrams and actual black-box and white-box test cases.
Three patterns, one app
SingletonInventory, Owner
There's only ever one Inventory and one Owner account in the whole app - both classes hide their own constructor and hand out the same shared instance to whoever asks for it, so nothing can accidentally spin up a second, out-of-sync copy.
ObserverShoppingCart -> InventoryUpdater
Checking out doesn't reach into the Inventory directly. The ShoppingCart (a Subject) just announces which books were bought, and an InventoryUpdater (an Observer) is the one that actually deducts them - the cart doesn't need to know how the inventory works, just that something's listening.
StateSilver, Gold extend Status
A customer's membership tier is its own object, not just a label. Cross 1,000 points and their Status silently swaps from Silver to Gold - the customer object never needed an if-statement to know which one it currently is.
Class diagram
The three hierarchies above, drawn out properly - hollow triangles for inheritance, real field and method signatures pulled from the source.
«abstract»
User
# username: String
# password: String
Customer
- points: int
- status: Status
- shoppingCart: ShoppingCart
+ setStatus(): void
«Singleton»
Owner
- customerList: List<Customer>
- inventory: Inventory
+ getInstance(): Owner
+ addCustomer(c)
«abstract»
Status
# name: String
Silver
name = "Silver"
Gold
name = "Gold"
«abstract»
Subject
# observer: Observer
+ updateInv(books): void
ShoppingCart
- booksInCart: List<Book>
- totalCost: double
- pointsOfCust: int
+ buyBooks(): void
+ redeemAndBuyBooks(): void
«abstract»
Observer
+ updateInventory(books)
InventoryUpdater
+ updateInventory(books)
ShoppingCart - - notifies - -> InventoryUpdater
Customer ◆ has a Status - starts Silver, flips to Gold at 1,000 pts
Customer ◆ has a ShoppingCart
Owner ○ shares Inventory - same singleton instance
Owner ○ keeps a list of Customer
Inventory ○ keeps a list of Book
Inside the app
The login screen has no real authentication behind it - admin / admin is hardcoded straight into the login button's handler and drops you into the Owner's screens; anything else is checked against the saved customer list instead.
As the Owner
Login → Owner Start Screen
├─ Books → add / remove titles
└─ Customers → add / delete accounts
As a Customer
Login → browse catalog
├─ Buy → cash receipt
└─ Redeem → points receipt
There's no real database - every customer and book is round-tripped through two flat text files, customers.txt and Books.txt, re-read on launch and rewritten after every add, buy, or redeem.
// the source even flags one gap itself - a stray comment reading "THE MISSING CODE!!!!" sits next to the line that was supposed to clear the customer list before re-reading it from disk
The rewards math
Both checkout paths land on their own receipt screen, and the math on it matches ShoppingCart's formulas exactly:
Built with a team of four, split across the GUI, the domain model, and the pattern implementations. Individual screens - the customer catalog view, the owner's book manager - started as their own standalone JavaFX apps before getting consolidated into one real entry point, MainBookStoreApp, the kind of throwaway scaffolding a class project goes through that never makes it into the final packaging. Every class went through a UML pass before it was written, and both black-box (does it produce the right output) and white-box (does it exercise every code path) test cases before it was called done.