一、需求
- 類比實現銀行業務調度系統邏輯,具體需求如下:
- 銀行內有6個業務視窗,1 - 4號視窗為普通視窗,5號視窗為快速視窗,6號視窗為VIP視窗。
- 有三種對應類型的客戶:VIP客戶,普通客戶,快速客戶(辦理如交水電費、電話費之類業務的客戶)。
- 非同步隨機產生各種類型的客戶,產生各類型使用者的機率比例為: VIP客戶 :普通客戶 :快速客戶 = 1 :6 :3。
- 客戶辦理業務所需時間有最大值和最小值,在該範圍內隨機設定每個VIP客戶以及普通客戶辦理業務所需的時間,快速客戶辦理業務所需時間為最小值(提示:辦理業務的過程可通過線程Sleep的方式類比)。
- 各類型客戶在其對應視窗按順序依次辦理業務。
- 當VIP(6號)視窗和快速業務(5號)視窗沒有客戶等待辦理業務的時候,這兩個視窗可以處理普通客戶的業務,而一旦有對應的客戶等待辦理業務的時候,則優先處理對應客戶的業務。
- 隨機產生客戶時間間隔以及業務辦理時間最大值和最小值自定,可以設定。
- 不要求實現GUI,只考慮系統邏輯實現,可通過Log方式展現程式運行結果。
二、物件導向分析與設計
- 有三種對應類型的客戶:VIP客戶,普通客戶,快速客戶 ,非同步隨機產生各種類型的客戶,各類型客戶在其對應視窗按順序依次辦理業務 。
- 首先,經常在銀行辦理業務的人更有利於理解本系統,例如,我經常陪老婆跑銀行,對銀行的這個業務算是比較熟悉了,我知道每一個客戶其實就是由銀行的一個取號機器產生號碼的方式來表示的。所以,我想到要有一個號碼管理器對象,讓這個對象不斷地產生號碼,就等於隨機產生了客戶。
- 由於有三類客戶,每類客戶的號碼編排都是完全獨立的,所以,我想到本系統一共要產生三個號碼管理器對象,各自管理一類使用者的排隊號碼。這三個號碼管理器對象統一由一個號碼機器進行管理,這個號碼機器在整個系統中始終只能有一個,所以,它要被設計成單例。
- 各類型客戶在其對應視窗按順序依次辦理業務 ,準確地說,應該是視窗依次叫號。
- 各個視窗怎麼知道該叫哪一個號了呢?它一定是問的相應的號碼管理器,即服務視窗每次找號碼管理器擷取當前要被服務的號碼。
- 如果我不是多次親身經曆銀行的這種業務,再加之積累了大量物件導向的應用開發經驗,我也不知道能否輕鬆進行這種設計,能否發掘出其中隱含的對象資訊,我真說不出具體的經驗是什麼,就是日積月累出來的一種感覺。難道這就是傳說中的:“只可意會,不可言傳?”
三、詳細設計NumberManager類
- 定義一個用於儲存上一個客戶號碼的成員變數和用於儲存所有等待服務的客戶號碼的隊列集合。
- 定義一個產生新號碼的方法和擷取馬上要為之服務的號碼的方法,這兩個方法被不同的線程操作了相同的資料,所以,要進行同步。
NumberMachine類
- 定義三個成員變數分別指向三個NumberManager對象,分別表示普通、快速和VIP客戶的號碼管理器,定義三個對應的方法來返回這三個NumberManager對象。
- 將NumberMachine類設計成單例。
CustomerType枚舉類
- 系統中有三種類型的客戶,所以用定義一個枚舉類,其中定義三個成員分別表示三種類型的客戶。
- 重寫toString方法,傳回型別的中文名稱。這是在後面編碼時重構出來的,剛開始不用考慮
ServiceWindow類
- 定義一個start方法,內部啟動一個線程,根據服務視窗的類別分別迴圈調用三個不同的方法。
- 定義三個方法分別對三種客戶進行服務,為了觀察運行效果,應詳細列印出其中的細節資訊。
MainClass類
- 用for迴圈建立出4個普通視窗,再建立出1個快速視窗和一個VIP視窗。
- 接著再建立三個定時器,分別定時去建立新的普通客戶號碼、新的快速客戶號碼、新的VIP客戶號碼。
Constants類
- 定義三個常量:MAX_SERVICE_TIME、MIN_SERVICE_TIME、COMMON_CUSTOMER_INTERVAL_TIME
四、編碼
package cn.itcast.bankqueue;public class Constants {public static int MAX_SERVICE_TIME = 10000; //10秒!public static int MIN_SERVICE_TIME = 1000; //1秒!/*每個普通視窗服務一個客戶的平均時間為5秒,一共有4個這樣的視窗,也就是說銀行的所有普通視窗合起來 * 平均1.25秒內可以服務完一個普通客戶,再加上快速視窗和VIP視窗也可以服務普通客戶,所以, * 1秒鐘產生一個普通客戶比較合理,*/public static int COMMON_CUSTOMER_INTERVAL_TIME = 1; }
import java.util.Random;import java.util.concurrent.Executors;import java.util.logging.Logger;/** * 沒有把VIP視窗和快速視窗做成子類,是因為實際業務中的普通視窗可以隨時被設定為VIP視窗和快速視窗。 * */public class ServiceWindow {private static Logger logger = Logger.getLogger("cn.itcast.bankqueue");private CustomerType type = CustomerType.COMMON;private int number = 1;public CustomerType getType() {return type;}public void setType(CustomerType type) {this.type = type;}public void setNumber(int number){this.number = number;}public void start(){Executors.newSingleThreadExecutor().execute(new Runnable(){public void run(){//下面這種寫法的運行效率低,最好是把while放在case下面while(true){switch(type){case COMMON:commonService();break;case EXPRESS:expressService();break;case VIP:vipService();break;}}}});}private void commonService(){String windowName = "第" + number + "號" + type + "視窗";System.out.println(windowName + "開始擷取普通任務!");Integer serviceNumber = NumberMachine.getInstance().getCommonManager().fetchNumber();if(serviceNumber != null ){System.out.println(windowName + "開始為第" + serviceNumber + "號普通客戶服務");int maxRandom = Constants.MAX_SERVICE_TIME - Constants.MIN_SERVICE_TIME;int serviceTime = new Random().nextInt(maxRandom)+1 + Constants.MIN_SERVICE_TIME;try {Thread.sleep(serviceTime);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(windowName + "完成為第" + serviceNumber + "號普通客戶服務,總共耗時" + serviceTime/1000 + "秒");}else{System.out.println(windowName + "沒有取到普通任務,正在空閑一秒");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}private void expressService(){Integer serviceNumber = NumberMachine.getInstance().getExpressManager().fetchNumber();String windowName = "第" + number + "號" + type + "視窗";System.out.println(windowName + "開始擷取快速任務!");if(serviceNumber !=null){System.out.println(windowName + "開始為第" + serviceNumber + "號快速客戶服務");int serviceTime = Constants.MIN_SERVICE_TIME;try {Thread.sleep(serviceTime);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(windowName + "完成為第" + serviceNumber + "號快速客戶服務,總共耗時" + serviceTime/1000 + "秒");}else{System.out.println(windowName + "沒有取到快速任務!");commonService();}}private void vipService(){Integer serviceNumber = NumberMachine.getInstance().getVipManager().fetchNumber();String windowName = "第" + number + "號" + type + "視窗";System.out.println(windowName + "開始擷取VIP任務!");if(serviceNumber !=null){System.out.println(windowName + "開始為第" + serviceNumber + "號VIP客戶服務");int maxRandom = Constants.MAX_SERVICE_TIME - Constants.MIN_SERVICE_TIME;int serviceTime = new Random().nextInt(maxRandom)+1 + Constants.MIN_SERVICE_TIME;try {Thread.sleep(serviceTime);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(windowName + "完成為第" + serviceNumber + "號VIP客戶服務,總共耗時" + serviceTime/1000 + "秒");}else{System.out.println(windowName + "沒有取到VIP任務!");commonService();}}}
import java.util.ArrayList;import java.util.List;public class NumberManager {private int lastNumber = 0;private List queueNumbers = new ArrayList();public synchronized Integer generateNewNumber(){//同步產生新號碼queueNumbers.add(++lastNumber);return lastNumber;}public synchronized Integer fetchNumber(){//同步抓取號碼if(queueNumbers.size()>0){return (Integer)queueNumbers.remove(0);}else{return null;}}}
public class NumberMachine {//號碼管理類,採用單例設計模式private NumberMachine(){}private static NumberMachine instance = new NumberMachine();public static NumberMachine getInstance(){return instance;}private NumberManager commonManager = new NumberManager();private NumberManager expressManager = new NumberManager();private NumberManager vipManager = new NumberManager();public NumberManager getCommonManager() {return commonManager;}public NumberManager getExpressManager() {return expressManager;}public NumberManager getVipManager() {return vipManager;}}
public enum CustomerType {//客戶枚舉類COMMON,EXPRESS,VIP;public String toString(){String name = null;switch(this){case COMMON:name = "普通";break;case EXPRESS:name = "快速";break;case VIP:name = name();break;}return name;}}
import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;import java.util.logging.Logger;public class MainClass {private static Logger logger = Logger.getLogger("cn.itcast.bankqueue");public static void main(String[] args) {//產生4個普通視窗for(int i=1;i<5;i++){ServiceWindow window = new ServiceWindow();window.setNumber(i);window.start();}//產生1個快速視窗ServiceWindow expressWindow = new ServiceWindow();expressWindow.setType(CustomerType.EXPRESS);expressWindow.start();//產生1個VIP視窗ServiceWindow vipWindow = new ServiceWindow();vipWindow.setType(CustomerType.VIP);vipWindow.start();//普通客戶拿號Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable(){public void run(){Integer serviceNumber = NumberMachine.getInstance().getCommonManager().generateNewNumber();/** * 採用logger方式,無法看到直觀的運行效果,因為logger.log方法內部並不是直接把內容列印出出來, * 而是交給內部的一個線程去處理,所以,列印出來的結果在時間順序上看起來很混亂。 *///logger.info("第" + serviceNumber + "號普通客戶正在等待服務!");System.out.println("第" + serviceNumber + "號普通客戶正在等待服務!");}},0,Constants.COMMON_CUSTOMER_INTERVAL_TIME, TimeUnit.SECONDS);//快速客戶拿號Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable(){public void run(){Integer serviceNumber = NumberMachine.getInstance().getExpressManager().generateNewNumber();System.out.println("第" + serviceNumber + "號快速客戶正在等待服務!");}},0,Constants.COMMON_CUSTOMER_INTERVAL_TIME * 2, TimeUnit.SECONDS);//VIP客戶拿號Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable(){public void run(){Integer serviceNumber = NumberMachine.getInstance().getVipManager().generateNewNumber();System.out.println("第" + serviceNumber + "號VIP客戶正在等待服務!");}},0,Constants.COMMON_CUSTOMER_INTERVAL_TIME * 6, TimeUnit.SECONDS);}}
五、總結
- 單例模式保證該類在記憶體中只產生一個執行個體對象,在單例模式設計的類中,構造方法必須私人化,在類內部建立該類對象,對外提供擷取該類對象的方法,另外單例模式在多線程訪問的情況下,注意同步問題。
- 開關語句switch()在JDK5.0中能接受枚舉 int byte short char 在jdk7.0中是支援String類型的
- 對於重複使用的代碼要抽取出來,封裝成方法,方便調用,方法的功能盡量細分。