面试题之如何用Java设计一个自动售货机
如何用Java設(shè)計(jì)一個(gè)自動售貨機(jī)程序是一個(gè)非常好的Java面試題。大多數(shù)情況會在面試比較senior的Java開發(fā)者的時(shí)候出現(xiàn)。在一個(gè)典型的代碼面試中,你需要在一定的時(shí)間內(nèi)根據(jù)對應(yīng)的條件完成相關(guān)的代碼。通常2到3小時(shí)內(nèi)(面試哪有這么多時(shí)間,哈哈),你需要產(chǎn)生設(shè)計(jì)文檔,可以工作的代碼已經(jīng)單元測試。這樣的Java面試的好處就是你能夠一次性檢測面試者的很多能力。為了能夠完成代碼的設(shè)計(jì),編碼以及單元測試,面試者需要在這三個(gè)方面都比較精通。
另外,這種真實(shí)的問題可以提升你面向?qū)ο蠓治龊驮O(shè)計(jì)能力的技能,假如你想成為一個(gè)很好的應(yīng)用開發(fā)者,那么這個(gè)技能就很重要。
要想用Java或者別的面向?qū)ο蟮恼Z言來設(shè)計(jì)一個(gè)自動售貨機(jī),你不僅僅需要了解最基本的東西,比如封裝(Encapsulation),多態(tài)(Polymorphism)或者繼承(Inheritance),你還需要理解如何使用抽象類和接口的細(xì)節(jié),這樣才能解決問題或者設(shè)計(jì)一個(gè)好的應(yīng)用。
通常這種問題,還會給你一個(gè)使用設(shè)計(jì)模式的機(jī)會,因?yàn)樵谶@個(gè)問題中你可以使用工廠模式去創(chuàng)建不同的售貨機(jī)。我在20個(gè)Java軟件開發(fā)的問題一文中曾今討論過這個(gè)問題,那之后,我收到了很多反饋關(guān)于解決這個(gè)問題的方案。
這篇文章,我們將會提供一個(gè)自動售貨機(jī)問題的解決方案。順便說一下,其實(shí)這個(gè)問題有很多種解決的方案,你應(yīng)當(dāng)在看本文之前自己先嘗試一下。你也需要先復(fù)習(xí)一下SOLID和OOPS的設(shè)計(jì)原則,我們會在代碼中使用到他們。當(dāng)你設(shè)計(jì)自動售貨機(jī)的時(shí)候,你會發(fā)現(xiàn)我們會用到其中很多的相關(guān)內(nèi)容。
另外,假如你對設(shè)計(jì)模式和原則感興趣,我推薦你看看Udemy的“Java設(shè)計(jì)模式”這門課。這門課包括SOLID的設(shè)計(jì)模式,比如開閉原則(open closed)以及里氏替代(Liskov Substitution),當(dāng)然也包括所有的面向?qū)ο蟮脑O(shè)計(jì)模式,比如裝飾,觀察,責(zé)任鏈等等。
問題陳述
你需要設(shè)計(jì)一個(gè)這樣的自動售貨機(jī):
需求部分是這個(gè)問題最重要的部分。你需要仔細(xì)地閱讀這個(gè)部分,然后對這個(gè)問題有一個(gè)高層的理解,然后思考如何來解決它。通常來說,需求部分都是不明確的,你需要通過閱讀問題的陳述來列出一系列的你自己理解的需求。
我們喜歡指出基本的需求,因?yàn)樗麄兒苋菀讈砀櫋R恍┬枨笫呛茈[式的,我們最好把他們在你的列表中顯式列出來。比如在這個(gè)問題中,假如售貨機(jī)沒有足夠的零錢來找回,那么他就不應(yīng)該接收相應(yīng)的請求。
很不幸,沒有什么課本或者課程來告訴你這些,你只有在真實(shí)的環(huán)境中做過這些才能知道。當(dāng)然,有兩本書曾幫助我改進(jìn)我的面向?qū)ο蠓治龊驮O(shè)計(jì)的能力,他們是《深入淺出面向?qū)ο蠓治龊驮O(shè)計(jì)》 (Head First Object Oriented Design and Analysis),假如你沒有相關(guān)的面向?qū)ο缶幊痰慕?jīng)驗(yàn),那么這本書非常值得推薦。
?
另外一本書是UML for Java Progrmmers,它是一本開發(fā)應(yīng)用和系統(tǒng)設(shè)計(jì)方面非常好的書,值得推薦。它的做著是Robert C. Martin。我已經(jīng)讀過他的很多本書,比如Clean Code, Clean Coder以及一本關(guān)于使用Agile進(jìn)行軟件開發(fā)的書。他在OOP方面的教學(xué)大概是最好的。
?
這本書有一個(gè)類似的問題:設(shè)計(jì)一個(gè)咖啡機(jī)。因此,假如你想有更多的實(shí)踐,或者希望提升你的面向?qū)ο蟮脑O(shè)計(jì)能力,你可以參考那個(gè)問題。那個(gè)問題也是一個(gè)很好的學(xué)習(xí)的作業(yè)。
方案和代碼
我的關(guān)于售貨機(jī)的Java實(shí)現(xiàn)包括以下的類和接口:
VendingMachine
它定義了售貨機(jī)的所有公用API,通常所有的高級功能應(yīng)該都在這個(gè)類里面。
VendingMachineImpl
售貨機(jī)的示例實(shí)現(xiàn)
VendingMachineFactory
這是一個(gè)工廠類,用來創(chuàng)建不同的售貨機(jī)
Item
Java的Enum關(guān)于售貨機(jī)服務(wù)的項(xiàng)目
Inventory
這個(gè)類用來展示庫存,用來創(chuàng)建售貨機(jī)中的案例和物品清單。
Coin
一個(gè)Java Enum用來表示支持的貨幣。
Bucket
一個(gè)用于容納兩個(gè)對象的參數(shù)化類。
NotFullPaidException
這是一個(gè)exception,主要用來表示一個(gè)用戶選擇了一個(gè)項(xiàng)目,但是沒有付足夠的錢。
NotSufficientChangeException
這個(gè)Exception用來表示售貨機(jī)沒有錢的用來找零。
SoldOutException
當(dāng)用戶選擇一個(gè)已經(jīng)賣完了的產(chǎn)品時(shí),會拋出這個(gè)exception
?
怎樣在Java中設(shè)計(jì)售貨機(jī)
下面就是完整的代碼,你可以測試一下這個(gè)代碼,如果有什么問題告訴我。
VendingMachine.java
它定義了售貨機(jī)的所有公用API,通常所有的高級功能應(yīng)該都在這個(gè)類里面。
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
一個(gè)VendingMachine接口實(shí)現(xiàn)示例,你可以在你的辦公室,公交車站,火車站以及公共的地方看到他
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
一個(gè)工廠類,用來創(chuàng)建不同的售貨機(jī)
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
一個(gè)Java的enum用來表示售貨機(jī)支持的貨幣
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
一個(gè)用來表示庫存的類,用來創(chuàng)建售貨機(jī)中的案例和物品清單。
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
一個(gè)帶參數(shù)的工具類,可以產(chǎn)生兩個(gè)對象
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
這是一個(gè)exception,主要用來表示一個(gè)用戶選擇了一個(gè)項(xiàng)目,但是沒有付足夠的錢。
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
這個(gè)Exception用來表示售貨機(jī)沒有錢的用來找零。
package vending; public class NotSufficientChangeException extends RuntimeException {private String message;public NotSufficientChangeException(String string) {this.message = string;}@Overridepublic String getMessage(){return message;}}SoldOutException.java
當(dāng)用戶選擇一個(gè)已經(jīng)賣完了的產(chǎn)品時(shí),會拋出這個(gè)exception
package vending; public class SoldOutException extends RuntimeException {private String message;public SoldOutException(String string) {this.message = string;}@Overridepublic String getMessage(){return message;}}?關(guān)于設(shè)計(jì)售貨機(jī)的第一部分就到這里結(jié)束了。在這個(gè)部分中,我們通過創(chuàng)建所有的類,以及寫相關(guān)的代碼解決了這個(gè)問題。但是單元測試和設(shè)計(jì)文檔并沒有做,關(guān)于這一部分你可以關(guān)注我們的第二部分。
假如你愿意的話,你可以為這個(gè)問題創(chuàng)建單元測試,或者在一個(gè)thread中運(yùn)行他,然后再建一個(gè)thread來調(diào)用它,這樣就類似模擬一個(gè)用戶。你也可以閱讀UML For Java Programmers中的相關(guān)內(nèi)容。
進(jìn)一步閱讀:
Design Pattern Library
From 0 to 1: Design Patterns – 24 That Matter – In Java
Java Design Patterns – The Complete Masterclass
?
更多原創(chuàng),敬請關(guān)注微信公眾號,每日更新業(yè)界最新資訊:
歡迎訪問個(gè)人小站:?https://donggeitnote.com/2020/07/06/javavendingmachine/
總結(jié)
以上是生活随笔為你收集整理的面试题之如何用Java设计一个自动售货机的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android hid自动重连,Andr
- 下一篇: java美元兑换,(Java实现) 美元