面试题之如何用Java设计一个自动售货机
如何用Java設計一個自動售貨機程序是一個非常好的Java面試題。大多數情況會在面試比較senior的Java開發者的時候出現。在一個典型的代碼面試中,你需要在一定的時間內根據對應的條件完成相關的代碼。通常2到3小時內(面試哪有這么多時間,哈哈),你需要產生設計文檔,可以工作的代碼已經單元測試。這樣的Java面試的好處就是你能夠一次性檢測面試者的很多能力。為了能夠完成代碼的設計,編碼以及單元測試,面試者需要在這三個方面都比較精通。
另外,這種真實的問題可以提升你面向對象分析和設計能力的技能,假如你想成為一個很好的應用開發者,那么這個技能就很重要。
要想用Java或者別的面向對象的語言來設計一個自動售貨機,你不僅僅需要了解最基本的東西,比如封裝(Encapsulation),多態(Polymorphism)或者繼承(Inheritance),你還需要理解如何使用抽象類和接口的細節,這樣才能解決問題或者設計一個好的應用。
通常這種問題,還會給你一個使用設計模式的機會,因為在這個問題中你可以使用工廠模式去創建不同的售貨機。我在20個Java軟件開發的問題一文中曾今討論過這個問題,那之后,我收到了很多反饋關于解決這個問題的方案。
這篇文章,我們將會提供一個自動售貨機問題的解決方案。順便說一下,其實這個問題有很多種解決的方案,你應當在看本文之前自己先嘗試一下。你也需要先復習一下SOLID和OOPS的設計原則,我們會在代碼中使用到他們。當你設計自動售貨機的時候,你會發現我們會用到其中很多的相關內容。
另外,假如你對設計模式和原則感興趣,我推薦你看看Udemy的“Java設計模式”這門課。這門課包括SOLID的設計模式,比如開閉原則(open closed)以及里氏替代(Liskov Substitution),當然也包括所有的面向對象的設計模式,比如裝飾,觀察,責任鏈等等。
問題陳述
你需要設計一個這樣的自動售貨機:
需求部分是這個問題最重要的部分。你需要仔細地閱讀這個部分,然后對這個問題有一個高層的理解,然后思考如何來解決它。通常來說,需求部分都是不明確的,你需要通過閱讀問題的陳述來列出一系列的你自己理解的需求。
我們喜歡指出基本的需求,因為他們很容易來跟蹤。一些需求是很隱式的,我們最好把他們在你的列表中顯式列出來。比如在這個問題中,假如售貨機沒有足夠的零錢來找回,那么他就不應該接收相應的請求。
很不幸,沒有什么課本或者課程來告訴你這些,你只有在真實的環境中做過這些才能知道。當然,有兩本書曾幫助我改進我的面向對象分析和設計的能力,他們是《深入淺出面向對象分析和設計》 (Head First Object Oriented Design and Analysis),假如你沒有相關的面向對象編程的經驗,那么這本書非常值得推薦。
?
另外一本書是UML for Java Progrmmers,它是一本開發應用和系統設計方面非常好的書,值得推薦。它的做著是Robert C. Martin。我已經讀過他的很多本書,比如Clean Code, Clean Coder以及一本關于使用Agile進行軟件開發的書。他在OOP方面的教學大概是最好的。
?
這本書有一個類似的問題:設計一個咖啡機。因此,假如你想有更多的實踐,或者希望提升你的面向對象的設計能力,你可以參考那個問題。那個問題也是一個很好的學習的作業。
方案和代碼
我的關于售貨機的Java實現包括以下的類和接口:
VendingMachine
它定義了售貨機的所有公用API,通常所有的高級功能應該都在這個類里面。
VendingMachineImpl
售貨機的示例實現
VendingMachineFactory
這是一個工廠類,用來創建不同的售貨機
Item
Java的Enum關于售貨機服務的項目
Inventory
這個類用來展示庫存,用來創建售貨機中的案例和物品清單。
Coin
一個Java Enum用來表示支持的貨幣。
Bucket
一個用于容納兩個對象的參數化類。
NotFullPaidException
這是一個exception,主要用來表示一個用戶選擇了一個項目,但是沒有付足夠的錢。
NotSufficientChangeException
這個Exception用來表示售貨機沒有錢的用來找零。
SoldOutException
當用戶選擇一個已經賣完了的產品時,會拋出這個exception
?
怎樣在Java中設計售貨機
下面就是完整的代碼,你可以測試一下這個代碼,如果有什么問題告訴我。
VendingMachine.java
它定義了售貨機的所有公用API,通常所有的高級功能應該都在這個類里面。
package vending;import java.util.List;/** * Decleare public API for Vending Machine */public interface VendingMachine {public long selectItemAndGetPrice(Item item);public void insertCoin(Coin coin);public List<Coin> refund();public Bucket<Item, List<Coin>> collectItemAndChange();public void reset(); }VendingMachineImpl.java
一個VendingMachine接口實現示例,你可以在你的辦公室,公交車站,火車站以及公共的地方看到他
package vending; import java.util.ArrayList; import java.util.Collections; import java.util.List; /*** Sample implementation of Vending Machine in Java* @author Javin Paul*/ public class VendingMachineImpl implements VendingMachine { private Inventory<Coin> cashInventory = new Inventory<Coin>();private Inventory<Item> itemInventory = new Inventory<Item>(); private long totalSales;private Item currentItem;private long currentBalance; public VendingMachineImpl(){initialize();}private void initialize(){ //initialize machine with 5 coins of each denomination//and 5 cans of each Item for(Coin c : Coin.values()){cashInventory.put(c, 5);}for(Item i : Item.values()){itemInventory.put(i, 5);}}@Overridepublic long selectItemAndGetPrice(Item item) {if(itemInventory.hasItem(item)){currentItem = item;return currentItem.getPrice();}throw new SoldOutException("Sold Out, Please buy another item");} @Overridepublic void insertCoin(Coin coin) {currentBalance = currentBalance + coin.getDenomination();cashInventory.add(coin);} @Overridepublic Bucket<Item, List<Coin>> collectItemAndChange() {Item item = collectItem();totalSales = totalSales + currentItem.getPrice();List<Coin> change = collectChange();return new Bucket<Item, List<Coin>>(item, change);}private Item collectItem() throws NotSufficientChangeException,NotFullPaidException{if(isFullPaid()){if(hasSufficientChange()){itemInventory.deduct(currentItem);return currentItem;} throw new NotSufficientChangeException("Not Sufficient change in Inventory");}long remainingBalance = currentItem.getPrice() - currentBalance;throw new NotFullPaidException("Price not full paid, remaining : ", remainingBalance);}private List<Coin> collectChange() {long changeAmount = currentBalance - currentItem.getPrice();List<Coin> change = getChange(changeAmount);updateCashInventory(change);currentBalance = 0;currentItem = null;return change;}@Overridepublic List<Coin> refund(){List<Coin> refund = getChange(currentBalance);updateCashInventory(refund);currentBalance = 0;currentItem = null;return refund;}private boolean isFullPaid() {if(currentBalance >= currentItem.getPrice()){return true;}return false;} private List<Coin> getChange(long amount) throws NotSufficientChangeException{List<Coin> changes = Collections.EMPTY_LIST;if(amount > 0){changes = new ArrayList<Coin>();long balance = amount;while(balance > 0){if(balance >= Coin.QUARTER.getDenomination() && cashInventory.hasItem(Coin.QUARTER)){changes.add(Coin.QUARTER);balance = balance - Coin.QUARTER.getDenomination();continue;}else if(balance >= Coin.DIME.getDenomination() && cashInventory.hasItem(Coin.DIME)) {changes.add(Coin.DIME);balance = balance - Coin.DIME.getDenomination();continue;}else if(balance >= Coin.NICKLE.getDenomination() && cashInventory.hasItem(Coin.NICKLE)) {changes.add(Coin.NICKLE);balance = balance - Coin.NICKLE.getDenomination();continue;}else if(balance >= Coin.PENNY.getDenomination() && cashInventory.hasItem(Coin.PENNY)) {changes.add(Coin.PENNY);balance = balance - Coin.PENNY.getDenomination();continue;}else{throw new NotSufficientChangeException("NotSufficientChange,Please try another product");}}}return changes;}@Overridepublic void reset(){cashInventory.clear();itemInventory.clear();totalSales = 0;currentItem = null;currentBalance = 0;} public void printStats(){System.out.println("Total Sales : " + totalSales);System.out.println("Current Item Inventory : " + itemInventory);System.out.println("Current Cash Inventory : " + cashInventory);} private boolean hasSufficientChange(){return hasSufficientChangeForAmount(currentBalance - currentItem.getPrice());}private boolean hasSufficientChangeForAmount(long amount){boolean hasChange = true;try{getChange(amount);}catch(NotSufficientChangeException nsce){return hasChange = false;}return hasChange;} private void updateCashInventory(List change) {for(Coin c : change){cashInventory.deduct(c);}}public long getTotalSales(){return totalSales;}}VendingMachineFactory.java
一個工廠類,用來創建不同的售貨機
package vending; /** * Factory class to create instance of Vending Machine, this can be extended to create instance of * different types of vending machines. * @author Javin Paul */public class VendingMachineFactory { public static VendingMachine createVendingMachine() { return new VendingMachineImpl(); }}Coin.java
一個Java的enum用來表示售貨機支持的貨幣
package vending; /*** Coins supported by Vending Machine.* @author Javin Paul*/ public enum Coin {PENNY(1), NICKLE(5), DIME(10), QUARTER(25);private int denomination;private Coin(int denomination){this.denomination = denomination;}public int getDenomination(){return denomination;} }Inventory.java
一個用來表示庫存的類,用來創建售貨機中的案例和物品清單。
package vending; import java.util.HashMap; import java.util.Map; /** * An Adapter over Map to create Inventory to hold cash and * Items inside Vending Machine * @author Javin Paul */ public class Inventory<T> { private Map<T, Integer> inventory = new HashMap<T, Integer>(); public int getQuantity(T item){ Integer value = inventory.get(item); return value == null? 0 : value ;} public void add(T item){ int count = inventory.get(item); inventory.put(item, count+1);} public void deduct(T item) { if (hasItem(item)) {int count = inventory.get(item);inventory.put(item, count - 1);} } public boolean hasItem(T item){return getQuantity(item) > 0;}public void clear(){ inventory.clear(); } public void put(T item, int quantity) { inventory.put(item, quantity); } }Bucket.java
一個帶參數的工具類,可以產生兩個對象
package vending; /** * A parameterized utility class to hold two different object. * @author Javin Paul */ public class Bucket<E1, E2> { private E1 first;private E2 second;public Bucket(E1 first, E2 second){this.first = first;this.second = second;} public E1 getFirst(){ return first; } public E2 getSecond(){return second;} }NotFullPaidException.java
這是一個exception,主要用來表示一個用戶選擇了一個項目,但是沒有付足夠的錢。
package vending; public class NotFullPaidException extends RuntimeException {private String message;private long remaining;public NotFullPaidException(String message, long remaining) {this.message = message;this.remaining = remaining;}public long getRemaining(){return remaining;}@Overridepublic String getMessage(){return message + remaining;} }NotSufficientChangeException.java
這個Exception用來表示售貨機沒有錢的用來找零。
package vending; public class NotSufficientChangeException extends RuntimeException {private String message;public NotSufficientChangeException(String string) {this.message = string;}@Overridepublic String getMessage(){return message;}}SoldOutException.java
當用戶選擇一個已經賣完了的產品時,會拋出這個exception
package vending; public class SoldOutException extends RuntimeException {private String message;public SoldOutException(String string) {this.message = string;}@Overridepublic String getMessage(){return message;}}?關于設計售貨機的第一部分就到這里結束了。在這個部分中,我們通過創建所有的類,以及寫相關的代碼解決了這個問題。但是單元測試和設計文檔并沒有做,關于這一部分你可以關注我們的第二部分。
假如你愿意的話,你可以為這個問題創建單元測試,或者在一個thread中運行他,然后再建一個thread來調用它,這樣就類似模擬一個用戶。你也可以閱讀UML For Java Programmers中的相關內容。
進一步閱讀:
Design Pattern Library
From 0 to 1: Design Patterns – 24 That Matter – In Java
Java Design Patterns – The Complete Masterclass
?
更多原創,敬請關注微信公眾號,每日更新業界最新資訊:
歡迎訪問個人小站:?https://donggeitnote.com/2020/07/06/javavendingmachine/
總結
以上是生活随笔為你收集整理的面试题之如何用Java设计一个自动售货机的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android hid自动重连,Andr
- 下一篇: cf战队服务器怎么分配位置,CF:全服最