黑馬程式設計師——面試題——2.銀行業務排程系統
---------- android培訓、java培訓、期待與您交流! ----------
銀行業務排程系統
--------------------------------------------------------------------------------------------------------------
面試經驗漫談:
android建立隨便自己編點玩具專案或小功能都可以,千萬不要照我們的簡歷去寫,因為android的招聘條件都不高,只要你會點都能找到工作,面試官不會特別為難你,但是,大量的雷同建立把面試官早就看煩了,你再出現一個相同的簡歷,他就會很反感。
---------------------------------------------------------------------------------------------------------------
專案需求闡述:
銀行業務排程系統
模擬實現銀行業務排程系統邏輯,具體需求如下:
Ø 銀行內有6個業務視窗,1 - 4號視窗為普通視窗,5號視窗為快速視窗,6號視窗為VIP視窗。
Ø 有三種對應型別的客戶:VIP客戶,普通客戶,快速客戶(辦理如交水電費、電話費之類業務的客戶)。
Ø 非同步隨機生成各種型別的客戶,生成各型別使用者的概率比例為: ↓
VIP客戶 :普通客戶 :快速客戶 = 1 :6 :3。
Ø 客戶辦理業務所需時間有最大值和最小值,在該範圍內隨機設定每個VIP客戶以及普通客戶辦理業務所需的時間,快速客戶辦理業務所需時間為最小值(提示:辦理業務的過程可通過執行緒Sleep的方式模擬)。
Ø 各型別客戶在其對應視窗按順序依次辦理業務。
Ø 當VIP(6號)視窗和快速業務(5號)視窗沒有客戶等待辦理業務的時候,這兩個視窗可以處理普通客戶的業務,而一旦有對應的客戶等待辦理業務的時候,則優先處理對應客戶的業務。
Ø 隨機生成客戶時間間隔以及業務辦理時間最大值和最小值自定,可以設定。
Ø 不要求實現GUI,只考慮系統邏輯實現,可通過Log方式展現程式執行結果。
--------------------------------------------------------------------------------------------------------------
面向物件的分析與設計
●有三種對應型別的客戶:VIP客戶,普通客戶,快速客戶 ,非同步隨機生成各種型別的客戶,各型別客戶在其對應視窗按順序依次辦理業務 。
○首先,經常在銀行辦理業務的人更有利於理解本系統,例如,我經常陪老婆跑銀行,對銀行的這個業務算是比較熟悉了,我知道每一個客戶其實就是由銀行的一個取號機器產生號碼的方式來表示的。所以,我想到要有一個號碼管理器物件,讓這個物件不斷地產生號碼,就等於隨機生成了客戶。
○由於有三類客戶,每類客戶的號碼編排都是完全獨立的,所以,我想到本系統一共要產生三個號碼管理器物件,各自管理一類使用者的排隊號碼。這三個號碼管理器物件統一由一個號碼機器進行管理,這個號碼機器在整個系統中始終只能有一個,所以,它要被設計成單例。
●各型別客戶在其對應視窗按順序依次辦理業務 ,準確地說,應該是視窗依次叫號。
○各個視窗怎麼知道該叫哪一個號了呢?它一定是問的相應的號碼管理器,即服務視窗每次找號碼管理器獲取當前要被服務的號碼。
○如果我不是多次親身經歷銀行的這種業務,再加之積累了大量面向物件的應用開發經驗,我也不知道能否輕鬆進行這種設計,能否發掘出其中隱含的物件資訊,我真說不出具體的經驗是什麼,就是日積月累出來的一種感覺。難道這就是傳說中的:“只可意會,不可言傳?”
---------------
類圖
//getInstance():單例應該有靜態方法,這個靜態成員getInstance()所以畫下劃線
---------------------------------------------------------------------------------------------------------------
專案需求設計
●NumberManager類
○定義一個用於儲存上一個客戶號碼的成員變數和用於儲存所有等待服務的客戶號碼的佇列集合。
○定義一個產生新號碼的方法和獲取馬上要為之服務的號碼的方法,這兩個方法被不同的執行緒操作了相同的資料,所以,要進行同步。
●NumberMachine類
○定義三個成員變數分別指向三個NumberManager物件,分別表示普通、快速和VIP客戶的號碼管理器,定義三個對應的方法來返回這三個NumberManager物件。
○將NumberMachine類設計成單例。
●ServiceWindow類
○定義一個start方法,內部啟動一個執行緒,根據服務視窗的類別分別迴圈呼叫三個不同的方法。
○定義三個方法分別對三種客戶進行服務,為了觀察執行效果,應詳細打印出其中的細節資訊。
●CustomerType列舉類
○系統中有三種類型的客戶,所以用定義一個列舉類,其中定義三個成員分別表示三種類型的客戶。
○重寫toString方法,返回型別的中文名稱。這是在後面編碼時重構出來的,剛開始不用考慮。
●Constants類
○定義三個常量:MAX_SERVICE_TIME
MIN_SERVICE_TIME
COMMON_CUSTOMER_INTERVAL_TIME
●MainClass類
○用for迴圈創建出4個普通視窗,再創建出1個快速視窗和一個VIP視窗。
○接著再建立三個定時器,分別定時去建立:新的普通客戶號碼
新的快速客戶號碼
新的VIP客戶號碼
----------------------------------------------------------------------------------------------------------------
示例程式碼小知識點
1.2個執行緒訪問相同的資料就會出問題,所以要實現互斥
修改前↓
修改後↓
2.private List<Integer> queueNumber = new ArrayList<Integer>();//定義動態陣列:list集合
//queueNumber:佇列
//List:變數型別,為什麼用它而不用ArrayList?面向介面程式設計,就是說以後不用ArrayList用LinkedList也可以
//變數型別儘量用父類或者介面上一級
3.執行緒池:一個池裡有多個執行緒
4.如果去做遊戲或安卓,建議把JDK1.5好好學學,因為新技術關鍵時刻可解決大問題
5.if else比 switch效率低
6.一個細節之處,CustomerTypes表示集合,CustomerType表示正常類,這個要注意
7.switch的資料型別只能是整數
8.for迴圈裡可以定義變數
----------------------------------------------------------------------------------------------------------------
示例程式碼
--------------------------------------------
(1)
NumberManager.java ↓
<span style="font-family:Microsoft YaHei;">package com.itheima.bank;
import java.util.ArrayList;
import java.util.List;
/**
* 號碼管理器 類
*/
public class NumberManager
{
private int lastNumber = 1;
//定義變數:上一次返回的號碼,所以是1因為號碼不能從0開始
private List<Integer> queueNumber = new ArrayList<Integer>();
//定義動態陣列:list集合
//private ArrayList<Integer> queueNumber = new ArrayList<Integer>();//也好使,問題在於ArrayList,水準不高
//queueNumber:佇列
//List:變數型別,為什麼用它而不用ArrayList?面向介面程式設計,就是說以後不用ArrayList用LinkedList也可以
//變數型別儘量用父類或者介面上一級
public synchronized Integer generateNewManager()
//產生新號碼(整數) 方法 //執行緒1
{
queueNumber.add(lastNumber);
return lastNumber++;
}
public synchronized Integer fetchServiceNumber()
//取服務號碼 方法 //執行緒2
//public synchronized int fetchServiceNumber()//萬一去取號取到remove(0)就會返回null ,null要轉成整數就會空指標異常
//Integer:接上,所以用它,自動拆箱和裝箱
{
Integer number = null;
if(queueNumber.size() > 0)
{
number = queueNumber.remove(0);
}
return number;
//返回值是取走的號
}
}</span>
-------------------------------------------------
(2)
NumberMachine.java ↓
<span style="font-family:Microsoft YaHei;">package com.itheima.bank;
/**
* 號碼機器 類
*/
public class NumberMachine
{
//定義3個物件,裡面有3個成員變數↓,返回3個方法(NumberManager)
private NumberManager commonManager = new NumberManager(); //普通客戶
private NumberManager expressManager = new NumberManager(); //快速客戶
private NumberManager vipManager = new NumberManager(); //VIP客戶
public NumberManager getCommonManager() {
return commonManager;
}
public NumberManager getExpressManager() {
return expressManager;
}
public NumberManager getVipManager() {
return vipManager;
}
//把上面的物件作成單例
private NumberMachine(){}//構造方法私有化
//它不能建立物件,方法不能直接呼叫,所以只能靜態方法才能呼叫所以下面是靜態的↓
public static NumberMachine getInstance()
//static:靜態方法返回它自己的物件(NumberMachine)
{
return instance;
}
//建立返回的物件
private static NumberMachine instance = new NumberMachine();
//instance:變數名
}</span>
-------------------------------------------------
(3)
ServiceWindow.java ↓
<span style="font-family:Microsoft YaHei;">package com.itheima.bank;
import java.util.Random;
import java.util.concurrent.Executors;
/**
* 服務視窗 類
*/
public class ServiceWindow
{
private CustomerType type = CustomerType.COMMON; //變數 客戶視窗型別
private int windowId = 1; //視窗號,預設為1號
public void setType(CustomerType type) {
this.type = type;
}
public void setWindowId(int windowId) {
this.windowId = windowId;
}
public void start()//開始執行緒
{
Executors.newSingleThreadExecutor().execute(new Runnable(){
//newSingleThreadExecutor:單獨的執行緒池,就是這個執行緒池裡只放一個執行緒
//搞執行緒都要Runnable物件,因為執行緒的程式碼都是Runnable裡面的
//{}:表示物件
public void run()
//Runnable物件實現Runnable介面,所以要實現Run方法
{
while(true)//迴圈取號
{
switch(type)
{
case COMMON://因為上面的type,所以現在是省略字首寫法,全寫:CustomerType.COMMON
commonService();
break;
case EXPRESS:
expressService();
break;
case VIP:
vipService();
break;
}
}
}
});
}
//普通客戶視窗
private void commonService()
{
String windowName = "第" + windowId + "號" + type + "視窗";
//String windowName = "第" + windowId + "號普通視窗";//硬編碼,程式碼移植性差
//type:返回值是toString方法(CustomerType.java)
Integer number = NumberMachine.getInstance().getCommonManager().fetchServiceNumber();
//號碼機器.獲取例項().拿到普通使用者().取號();
//Integer:接NumberManager類的自動拆箱和裝箱原理所以也用它
System.out.println(windowName + "正在獲取任務");
if(number != null)
{
System.out.println(windowName + "為第" + number + "個" + "普通" +"客戶服務");
long beginTime = System.currentTimeMillis();
int maxRand =Constants.MAX_SERVICE_TIME - Constants.MIN_SERVICE_TIME;
//max:最大隨機值
//Constants.MAX_SERVICE_TIME(10000) - Constants.MIN_SERVICE_TIME(1000)=9000
long serveTime = new Random().nextInt(maxRand) + 1 + Constants.MIN_SERVICE_TIME;
//由上面得出產出的隨機值是0-8999,希望是1-9000,所以+1,等於9000,在加上MIN_SERVICE_TIME(1000),一共10000
try {
Thread.sleep(serveTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
long costTime = System.currentTimeMillis() - beginTime;
System.out.println(windowName + "為第" + number + "個" + "普通" +"客戶完成服務,耗時" + costTime/1000 + "秒");
}
else
{
System.out.println(windowName + "沒有取到任務,先休息1秒鐘");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//快速客戶視窗
private void expressService()
{
String windowName = "第" + windowId + "號" + type + "視窗";
//String windowName = "第" + windowId + "號普通視窗";//硬編碼,程式碼移植性差
//type:返回值是toString方法(CustomerType.java)
Integer number = NumberMachine.getInstance().getExpressManager().fetchServiceNumber();
//號碼機器.獲取例項().拿到普通使用者().取號();
//Integer:接NumberManager類的自動拆箱和裝箱原理所以也用它
System.out.println(windowName + "正在獲取任務");
if(number != null)
{
System.out.println(windowName + "為第" + number + "個" + type +"客戶服務");
long beginTime = System.currentTimeMillis();
//int maxRand =Constants.MAX_SERVICE_TIME - Constants.MIN_SERVICE_TIME;
//long serveTime = new Random().nextInt(maxRand) + 1 + Constants.MIN_SERVICE_TIME;
try {
Thread.sleep(Constants.MIN_SERVICE_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
long costTime = System.currentTimeMillis() - beginTime;
System.out.println(windowName + "為第" + number + "個" + type +"客戶完成服務,耗時" + costTime/1000 + "秒");
}
else
{
System.out.println(windowName + "沒有取到任務");
commonService();
}
}
//VIP客戶視窗
private void vipService()
{
String windowName = "第" + windowId + "號" + type + "視窗";
//String windowName = "第" + windowId + "號普通視窗";//硬編碼,程式碼移植性差
//type:返回值是toString方法(CustomerType.java)
Integer number = NumberMachine.getInstance().getVipManager().fetchServiceNumber();
//號碼機器.獲取例項().拿到普通使用者().取號();
//Integer:接NumberManager類的自動拆箱和裝箱原理所以也用它
System.out.println(windowName + "正在獲取任務");
if(number != null)
{
System.out.println(windowName + "為第" + number + "個" + type +"客戶服務");
long beginTime = System.currentTimeMillis();
//int maxRand =Constants.MAX_SERVICE_TIME - Constants.MIN_SERVICE_TIME;
//long serveTime = new Random().nextInt(maxRand) + 1 + Constants.MIN_SERVICE_TIME;
try {
Thread.sleep(Constants.MIN_SERVICE_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
long costTime = System.currentTimeMillis() - beginTime;
System.out.println(windowName + "為第" + number + "個" + type +"客戶完成服務,耗時" + costTime/1000 + "秒");
}
else
{
System.out.println(windowName + "沒有取到任務");
commonService();
}
}
}</span>
-------------------------------------------------
(4)
CustomerType.java ↓
<span style="font-family:Microsoft YaHei;">package com.itheima.bank;
/**
* 客戶型別 列舉
*/
public enum CustomerType
{
COMMON,EXPRESS,VIP;
public String toString()
{
switch(this)//this:單獨這個物件覆蓋toString
{
case COMMON:
return "普通";
case EXPRESS:
return "快速";
case VIP:
return name();//VIP直接返回VIP,名字更直觀
//name方法就是返回自己的名字
}
return null;
}
}</span>
-------------------------------------------------
(5)
Constants.java ↓
<span style="font-family:Microsoft YaHei;">package com.itheima.bank;
/**
* 常量 類
*/
public class Constants
{
public static int MAX_SERVICE_TIME = 10000; //最大服務時間:10秒
public static int MIN_SERVICE_TIME = 1000; //最小服務時間:1秒
public static int COMMON_CUSTOMER_INTERVAL_TIME = 1; //普通使用者間隔時間:1秒
}</span>
-------------------------------------------------
(6)
MainClass.java ↓
<span style="font-family:Microsoft YaHei;">package com.itheima.bank;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/*
* 主方法 類
*/
public class MainClass
{
public static void main(String[] args)
{
/*準備3個服務視窗*/
for(int i=1; i<5; i++)
{
ServiceWindow commonwindow = new ServiceWindow();
//for迴圈裡可以定義變數
commonwindow.setWindowId(i);
commonwindow.start();
}
ServiceWindow expressWindow = new ServiceWindow();
expressWindow.setType(CustomerType.EXPRESS);
expressWindow.start();
ServiceWindow vipWindow = new ServiceWindow();
vipWindow.setType(CustomerType.VIP);
vipWindow.start();
/*模擬3種客戶*/
//普通客戶
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new Runnable(){
public void run() {//來人(叫號)
Integer number = NumberMachine.getInstance().getCommonManager().generateNewManager();
System.out.println(number + "號普通客戶等待服務!");
}},
0, //來的第一個人就開始,0索引是1
Constants.COMMON_CUSTOMER_INTERVAL_TIME,
//1秒生成1個(Constants類已定義為1)
TimeUnit.SECONDS
);
//快速客戶
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new Runnable(){
public void run() {
Integer number = NumberMachine.getInstance().getExpressManager().generateNewManager();
System.out.println(number + "號快速客戶等待服務!");
}},
0,
Constants.COMMON_CUSTOMER_INTERVAL_TIME * 2,
//2秒生成1個
TimeUnit.SECONDS
);
//VIP客戶
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new Runnable(){
public void run() {
Integer number = NumberMachine.getInstance().getVipManager().generateNewManager();
System.out.println(number + "號VIP客戶等待服務!");
}},
0,
Constants.COMMON_CUSTOMER_INTERVAL_TIME * 6,
//隨機生成概率比例為{ VIP: 普通 :快遞 = 1 : 6 : 3 }
//6秒生成一個
TimeUnit.SECONDS
);
}
}
</span>