这是一道银行业务调度系统的题目:
模拟实现银行业务调度系统逻辑,具体需求如下:
Ø 银行内有6个业务窗口,1- 4号窗口为普通窗口,5号窗口为快速窗口,6号窗口为VIP窗口。
Ø 有三种对应类型的客户:VIP客户,普通客户,快速客户(办理如交水电费、电话费之类业务的客户)。
Ø 异步随机生成各种类型的客户,生成各类型用户的概率比例为:
VIP客户 :普通客户 :快速客户 = 1 :6 :3。
Ø 客户办理业务所需时间有最大值和最小值,在该范围内随机设定每个VIP客户以及普通客户办理业务所需的时间,快速客户办理业务所需时间为最小值(提示:办理业务的过程可通过线程Sleep的方式模拟)。
Ø 各类型客户在其对应窗口按顺序依次办理业务。
Ø 当VIP(6号)窗口和快速业务(5号)窗口没有客户等待办理业务的时候,这两个窗口可以处理普通客户的业务,而一旦有对应的客户等待办理业务的时候,则优先处理对应客户的业务。
Ø 随机生成客户时间间隔以及业务办理时间最大值和最小值自定,可以设置。
Ø 不要求实现GUI,只考虑系统逻辑实现,可通过Log方式展现程序运行结果。
这个题目和上一个交通管理系统的题目对比,更加复杂一些,因为其中有不同类型的客户,使得不能简单的定义一个集合进行获取操作。在没看视频前我一直在思考怎样使得客户和窗口间有一种对应的关系,想了很久,觉得假设银行有管理人,当人来的时候,根据不同的客户对其进行编号,比如普通客户1号,VIP客户3号,然后这个管理人看6个窗口是否有人正在存取操作,如果没有,则叫对应的客户上窗口操作。那么窗口就必须定义一个是否有人的状态,管理人可以根据这个状态进行操作。主要的功能都是在这个管理人,即客户和窗口对映关系上。这样一来又有了新的麻烦,这个管理人定义成什么类呢?它如何获取窗口状态?
看来视频讲解后才发现,先前那个交通管理系统之所以要定义灯的状态是需要让路获取到这个状态,这里窗口可以自己实现叫号,它知道自己当前的装备,所以不需要定义这么一个是否有人的状态变量。这其实也是一种惯性思维,面向对象的思维是多元化的,不存在固定的套路,只要能实现功能,并且代码简单都是好程序,所以发散思维尤为重要,不能陷入僵化的思维模式。
此题中客户和窗口的对映关系是最主要的内容,只要解决了这个问题,其它的都是用多线程写写代码的问题,只要细心点就OK了。客户要一个不同类型的取号方式,那么窗口就可以根据自身属性的不同叫号,叫符合自身属性的客户号,并且在叫到号后操作一段时间。
那么,这个问题就有3个主要的类:
1. 客户取号,窗口叫号的号码管理器
2. 创建3个不同类型的管理器,并提供给不同客户和窗口使用
3. 窗口类,实现叫号功能,并在叫到号后操作一段时间,快速窗口和VIP窗口在叫自己属性的客户无人后还要调用一次普通窗口的方法。
这个号码管理的类,实现取号、叫号功能:
package it.cast.bank;
import java.util.ArrayList;
import java.util.List;
public class NumberManager {
private int lastNumber = 1;
private List<Integer> queueNumber = new ArrayList<Integer>(); //尽量面向对象,用接口List体现水平
//客户取号的方法
public synchronized Integer generateNewNumber() {
queueNumber.add(lastNumber);
return lastNumber++;
}
//窗口叫号需要调用的方法,因为同步,必须叫锁
public synchronized Integer fetchServerNumber(){
Integer number = null;
if(queueNumber.size()>0)
number = queueNumber.remove(0);
return number;
}
}
号码机器,实现了3中不同类型的号码,并采用单例设计模式:
package it.cast.bank;
public class NumberMachine {
// 3个不同类型的号码
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;
}
//单例模式,只能通过类名调用
private NumberMachine(){}
private static NumberMachine instance = new NumberMachine();
public static NumberMachine getInstance(){
return instance;
}
}
使用枚举列出客户类型,显得更加专业:
package it.cast.bank;
public enum CustomerType {
COMMON,EXPRESS,VIP;
public String toString(){
switch (this){
case COMMON:
return "普通";
case EXPRESS:
return "快速";
case VIP:
return "VIP";
default:
return null;
}
}
}
窗口是代码最复杂的类,不过思维确不复杂,只要不断叫号,叫到号之后等待一段时间即可:
package it.cast.bank;
import java.util.Random;
import java.util.concurrent.Executors;
public class ServiceWindow {
//创建窗口必须明确类型
public void setType(CustomerType type) {
this.type = type;
}
//创始话窗口号
public void setWindowID(int windowID) {
this.windowID = windowID;
}
private CustomerType type = CustomerType.COMMON;
private int windowID = 1;
public void start() {
//用线程池的方式使用多线程
Executors.newSingleThreadExecutor().execute(new Runnable() {
public void run() {
while (true) {
switch (type) {
case COMMON:
commonService();
break;
case EXPRESS:
expressService();
break;
case VIP:
vipService();
break;
default:
System.out.println("服务窗口不存在!");
break;
}
}
}
});
}
//如果叫号成功,如果成功,执行一段时间,再继续叫号
private void commonService() {
String windowName = "第" + windowID + "号" + type + "窗口";
System.out.println(windowName+"正在获取普通客户任务---");
Integer number = NumberMachine.getInstance().getCommonManager().fetchServerNumber();
if(number!=null){
long beginTime = System.currentTimeMillis();
int maxRan = Constants.MAX_SERVICE_TIME - Constants.MIN_SERVICE_TIME;
long serveTime = new Random().nextInt(maxRan) + 1 + Constants.MIN_SERVICE_TIME ;
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 + "窗口";
System.out.println(windowName+"正在获取快速客户任务---");
Integer number = NumberMachine.getInstance().getExpressManager().fetchServerNumber();
if(number!=null){
long beginTime = System.currentTimeMillis();
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 + "没有取到"+type+"客户任务");
commonService();
}
}
//如果叫号成功,如果成功,执行一段时间,再继续叫号,只是获取的对象不同
private void vipService() {
String windowName = "第" + windowID + "号" + type + "窗口";
System.out.println(windowName+"正在获取VIP客户任务---");
Integer number = NumberMachine.getInstance().getVipManager().fetchServerNumber();
if(number!=null){
long beginTime = System.currentTimeMillis();
int maxRan = Constants.MAX_SERVICE_TIME - Constants.MIN_SERVICE_TIME;
long serveTime = new Random().nextInt(maxRan) + 1 + Constants.MIN_SERVICE_TIME ;
try {
Thread.sleep(serveTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
long costTime = System.currentTimeMillis() - beginTime;
System.out.println(windowName + "为第" + number + "个"
+ type + "客户服务,用时" + costTime/1000 + "秒");
}else{
System.out.println(windowName + "没有取到"+type+"客户任务");
commonService();
}
}
}
package it.cast.bank;
public class Constants {
public static int MAX_SERVICE_TIME = 10000;
public static int MIN_SERVICE_TIME = 1000;
public static int COMMON_CUSTOMER_INTERVAL_TIME = 1;
}
最后创建窗口对象和客户来运行:
package it.cast.bank;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class MainClass {
public static void main(String[] args) {
//创建4个普通窗口
for (int i = 1; i < 5; i++) {
ServiceWindow commonWindow = new ServiceWindow();
commonWindow.setType(CustomerType.COMMON);
commonWindow.setWindowID(i);
commonWindow.start();
}
//创建1个快速窗口
ServiceWindow expressWindow = new ServiceWindow();
expressWindow.setType(CustomerType.EXPRESS);
expressWindow.setWindowID(5);
expressWindow.start();
//创建1个VIP窗口
ServiceWindow vipWindow = new ServiceWindow();
vipWindow.setType(CustomerType.VIP);
vipWindow.setWindowID(6);
vipWindow.start();
//用线程池中的时间控制器,每个一段时间产生一个客户
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new Runnable(){
public void run(){
Integer number = NumberMachine.getInstance().getCommonManager().generateNewNumber();
System.out.println("第"+number+"号普通客户正在等待服务!");
}
},
0,
Constants.COMMON_CUSTOMER_INTERVAL_TIME,
TimeUnit.SECONDS);
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new Runnable(){
public void run(){
Integer number = NumberMachine.getInstance().getExpressManager().generateNewNumber();
System.out.println("第"+number+"号快速客户正在等待服务!");
}
},
0,
Constants.COMMON_CUSTOMER_INTERVAL_TIME*3,
TimeUnit.SECONDS);
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new Runnable(){
public void run(){
Integer number = NumberMachine.getInstance().getVipManager().generateNewNumber();
System.out.println("第"+number+"号VIP客户正在等待服务!");
}
},
0,
Constants.COMMON_CUSTOMER_INTERVAL_TIME*6,
TimeUnit.SECONDS);
}
}
运行结果很成功,各个对映的客户到对映的窗口,也有快速、VIP窗口没人时办理普通客户的情况:
第1号普通窗口正在获取普通客户任务---
第2号普通窗口正在获取普通客户任务---
第3号普通窗口正在获取普通客户任务---
第6号VIP窗口正在获取VIP客户任务---
第4号普通窗口正在获取普通客户任务---
第4号普通窗口没有取到普通客户任务,先休息1秒钟 !
第1号普通窗口没有取到普通客户任务,先休息1秒钟 !
第5号快速窗口正在获取快速客户任务---
第5号快速窗口没有取到快速客户任务
第5号快速窗口正在获取普通客户任务---
第5号快速窗口没有取到普通客户任务,先休息1秒钟 !
第6号VIP窗口没有取到VIP客户任务
第6号VIP窗口正在获取普通客户任务---
第6号VIP窗口没有取到普通客户任务,先休息1秒钟 !
第3号普通窗口没有取到普通客户任务,先休息1秒钟 !
第2号普通窗口没有取到普通客户任务,先休息1秒钟 !
第1号普通客户正在等待服务!
第1号VIP客户正在等待服务!
第1号快速客户正在等待服务!
第1号普通窗口正在获取普通客户任务---
第3号普通窗口正在获取普通客户任务---
第3号普通窗口没有取到普通客户任务,先休息1秒钟 !
第6号VIP窗口正在获取VIP客户任务---
第5号快速窗口正在获取快速客户任务---
第4号普通窗口正在获取普通客户任务---
第4号普通窗口没有取到普通客户任务,先休息1秒钟 !
在这个系统中,对映关系的思维方式才是最难想到的地方,这还是需要不断的实践,了解在实际生活中是怎么操作这些事物的,再结合面向对象的思维方式来解决这个问题。
这个系统用的最多的技术就是线程池,只要记住了Executors这个关键的类就很好办,并不需要我们记住它其中的具体办法,eclipse会有提示,只要认识几个英文单词就能大概知道这个线程的意思。如此类使用的newScheduledThreadPool(1).scheduleAtFixedRate()就是一个时间控制器,每隔固定的时间执行一次,不需要我们再用sleep套上循环来使用,新的技术出现肯定是方便使用,我们没那么多精力知道它是如何实现的,但是我们要记住它的关键字,和其使用方法。