银行业务调度系统
先来分析先现实生活中的银行系统:
1. 当你进入一家银行,首先干嘛??当然是到服务机选择你丫要办理的业务了(好了,服务机是一个对象吧!即下面NumberMachine类;;;
这里不理解的话看下面具体类的解释)
2. 当来到服务机面前你要干嘛??当然是选择你要办理的业务类型了(普通、快速和vip);当你一选择某种业务,服务机就会给你一个号,
这个号就是你要等待服务的号(恭喜你又知道了一个类NumberManager;;;)
3. 好了这时你就等吧,该轮到窗口里面的服务人员了!!!你要明白一点!服务机不仅是给客户用的,还得给服务人员用啊,
他们得从服务机那里得到号吧,然后才能叫号吧!!!
(中奖了!!又一个类来了,ServiceWindow类,它是用来模拟窗口里面服务人员的,并且是随机的到服务机中取号再叫号)
这时整个系统就跑起来啦!!!!
注意:还有三个类,分别是CustomerType、Constants和MainTest。CustomerType是枚举类,是用来定义三个窗口类型的,
你想啊!!普通窗口、快速窗口和VIP窗口它们三是不是唯一的,所以用枚举定义三个窗口类型
(对枚举不太了解的可以参考我的这篇博客JAVA之Myeclipse中Junit、静态导入、枚举和单例设计模式);
Constants是用来定义ServiceWindow服务时给每种类型的客户办理业务所需的时间(最大时间和最小时间);MainTest类是个测试类,
让整个系统跑起来的类,建议读者,先看这个类,有助于理解
模拟实现银行业务调度系统逻辑,具体需求如下:
A. 假设银行内有6个业务窗口,1 - 4号窗口为普通窗口,5号窗口为快速窗 口,6号窗口为VIP窗口。
B. 有三种对应类型的客户:VIP客户,普通客户,快速客户(办理如交水电费、电话费之类业务的客户)。
C. 异步随机生成各种类型的客户,生成各类型用户的概率比例为:
VIP客户 :普通客户 :快速客户 = 1 :6 :3。
D. 客户办理业务所需时间有最大值和最小值,在该范围内随机设定每个VIP客户以及普通客户办理业务所需的时间,
快速客户办理业务所需时间为最小值(提示:办理业务的过程可通过线程Sleep的方式模拟)。
E. 当VIP(6号)窗口和快速业务(5号)窗口没有客户等待办理业务的时候,这两个窗口可以处理普通客户的业务,
而一旦有对应的客户等待办理业务的时候,则优先处理对应客户的业务。
F. 随机生成客户时间间隔以及业务办理时间最大值和最小值自定,可以设置。
面向对象的分析与设计:
1. NumberManager类:这个类其实就是模拟客户的,号是由客户到服务机点击选择要办理的业务产生的,
所以这里直接用产生号的类来模拟由人产生号。
定义一个用于存储上一个客户号码的成员变量和用于存储所有等待服务的客户号码的队列集合。
定义一个产生新号码的方法和获取马上要为之服务的号码的方法,这两个方法被不同的线程操作了相同的数据,所以,要进行同步。
具体看代码分析:
/*
下面有2个方法,分别是产生新号码的方法和获取马上要为之服务的号码的方法
这两个方法被不同的线程操作,又涉及到了操作同一个数据queueLists,
所以2个方法要进行同步
* */
public class NumberManager {
//定义一个用于存储上一个客户号码的成员变量
private int lastNumber=1;
//用于存储所有等待服务的客户号码队列集合
private List<Integer> queueLists=new ArrayList<Integer>();
//定义一个产生新号码的方法
public synchronized Integer generateNewNumber()
{
//将产生的号码存储到集合中
queueLists.add(lastNumber);
return lastNumber++;
}
//获取马上要为之服务的号码的方法,就是窗口要获取号码时要调用的方法
public synchronized Integer fetchServerNumber()
{
Integer number=null;
if(queueLists.size()>0){
number=queueLists.remove(0);
}
return number;
}
}
2. NumberMachine类:这个类是用来装NumberManager用的,你想啊,服务机上的那些选项不都是包括在服务机上的吗?
所以将三种不同的客户类型都在这个类中实例化,然后对外提供出去;
定义三个成员变量分别指向三个NumberManager对象,分别表示普通、快速和VIP客户的号码管理器,
定义三个对应的方法来返回这三个NumberManager对象。
将NumberMachine类设计成单例,并切是饿汉式(不理解单例设计模式可以看下我写的博客
黑马程序员_JAVA之Myeclipse中Junit、静态导入、枚举和单例设计模式)
具体看代码:
public class NumberMachine {
//定义三个成员变量分别指向三个NumberManager对象
//分别代表普通、快速和VIP客户的号码管理器,因为产生号是在服务机上产生的啊!!
//所以要在服务机类中实例化产生号的类的三个实例对象
private NumberManager commonManager=new NumberManager();
private NumberManager expressManager=new NumberManager();
private NumberManager vipManager=new NumberManager();
//定义三个对应的方法来返回这三个NumberManager对象
public NumberManager getCommonManager() {
return commonManager;
}
public NumberManager getExpressManager() {
return expressManager;
}
public NumberManager getVipManager() {
return vipManager;
}
//将这个类设计成单例设计模式,先要有个静态的实例对象,并且是饿汉式
private static NumberMachine instance=new NumberMachine();
//将构造函数私有化
private NumberMachine(){}
//要提供一个方法对外将这个实例对象提供出去,并且是静态的,因为只有类名可以调用
public static NumberMachine getInstance()
{
return instance;
}
}
3.ServiceWindow类:该类是模拟银行窗口里面的服务人员,你想啊!!你在服务机上选择了业务,
那么谁来为你服务呢,当然是各个窗口里面的服务人员了,这里用窗口来代替服务人员。
注意:这里的ServiceWindow类也要访问NumberMachine类中的三个不同的客户产生的号。因为要判断不同的类型,
这里定义一个枚举类来唯一标示这三种类型
定义一个start方法,内部启动一个线程,根据服务窗口的类别分别循环调用三个不同的方法。
在方法里面利用switc语句来判断到底是哪种类型的客户需要服务
定义三个方法分别对三种客户进行服务,为了观察运行效果,应详细打印出其中的细节信息。
具体看代码分析:
/*
定义一个start方法,内部启动一个线程,根据服务窗口的类别分别循环调用三个不同的方法
* */
public class ServiceWindow {
//定义一个枚举值,来表示唯一的窗口类别
private CustomerType type=CustomerType.COMMON;
//定义一个表示几号窗口的变量,初始化为1
private int windowId=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() {
@Override
public void run() {
while(true)
{
//下面的客户只有三种类型,所以将客户的类型定义成了一个枚举:
//分别是普通客户(COMMON)、快速客户(EXPRESS)、VIP客户(VIP)
switch(type)
{
case COMMON:
commonServiceWindow();
break;
case EXPRESS:
expressServiceWindow();
break;
case VIP:
vipServiceWindow();
break;
}
}
}
});
}
//普通客户受到服务后具体干了什么事:
private void commonServiceWindow()
{
//定义一个窗口的具体信息名,说明它是普通窗口
String windowName="第"+windowId+"号"+type+"窗口";
/*
1.在MainTest类的main函数中普通客户已经叫号了,你还不为他服务??
(即Executors.newScheduledThreadPool(1).scheduleAtFixedRate),
2.是个人都知道,银行有个服务机。主要是给客户用的选择办理不同业务用的,
a.可是它也是给窗口的银行服务人员用的啊,他总得知道谁叫了号吧!(即使没有人来,没人叫号)他要先找到服务机吧??
即NumberMachine.getInstance()
b.然后他要知道是哪种类型的客户吧
即NumberMachine.getInstance().getCommonManager()
c.再然后他就要获取号了吧
即Integer number=NumberMachine.getInstance().getCommonManager().fetchServerNumber();
3.下面就对这个number是否存在进行处理吧!!
* */
Integer number=NumberMachine.getInstance().getCommonManager().fetchServerNumber();
//打印一条信息,让你爽下,知道哪中类型的窗口为哪种类型的客户服务
System.out.println(windowName+"正在获取任务");
/*
好吧!服务人员已经取到号了,下面他就要判断这个号是否存在吧,有就为普通客户服务,
没有!!呵呵呵.....喝口水,抽枝烟吧!!!
* */
if(number!=null)
{
//在控制台打印一条信息说明:哪个窗口正在为哪种类型的客户服务
System.out.println(windowName+"正在为第"+number+"号普通客户服务");
/*
从这开始
下面的代码是让这个窗口睡眠几秒钟,
为的是模拟为每个普通客户服务的时间(服务的时间是最大值和最小值之间的任意一个1~10秒钟)
* */
long startTime=System.currentTimeMillis();
//最大的随机值
int maxRandom=Constants.MAX_SERVICE_TIME-Constants.MIN_SERVICE_TIME;
//服务的时间是1秒到10秒钟
long serviceTime=new Random().nextInt(maxRandom)+1+Constants.MIN_SERVICE_TIME;
try {
Thread.sleep(serviceTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
long costTime=System.currentTimeMillis()-startTime;
到这结束
//下面打印的信息代表的是具体为客户干了些什么事!!
//注意:打印的信息是为哪个窗口为哪种类型的客户服务,并耗时多长
System.out.println(windowName+"为第"+number+"号普通完成客户服务,耗时"+costTime/1000+"秒");
}else{//如果没有产生新号,也就是窗口叫号没人理,那么普通窗口休息一会
System.out.println(windowName+"没有取到任务,休息1秒钟!!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//快速客户受到服务后具体干了什么事:
private void expressServiceWindow()
{
//定义一个窗口的具体信息名,说明它是快速窗口
String windowName="第"+windowId+"号"+type+"窗口";
/*
1.在MainTest类的main函数中快速客户已经叫号了,你还不为他服务??
(即Executors.newScheduledThreadPool(1).scheduleAtFixedRate),
2.是个人都知道,银行有个服务机。主要是给客户用的选择办理不同业务用的,
a.可是它也是给窗口的银行服务人员用的啊,他总得知道谁叫了号吧!(即使没有人来,没人叫号)他要先找到服务机吧??
即NumberMachine.getInstance()
b.然后他要知道是哪种类型的客户吧
即NumberMachine.getInstance().getExpressManager()
c.再然后他就要获取号了吧
即Integer number=NumberMachine.getInstance().getExpressManager().fetchServerNumber();
3.下面就对这个number是否存在进行处理吧!!
* */
Integer number=NumberMachine.getInstance().getExpressManager().fetchServerNumber();
//打印一条信息,让你爽下,知道哪中类型的窗口为哪种类型的客户服务
System.out.println(windowName+"正在获取任务");
/*
好吧!服务人员已经取到号了,下面他就要判断这个号是否存在吧,有!就为快速客户服务,
没有!!呵呵呵.....不好意思,为普通客户服务!!!
* */
if(number!=null)
{
//在控制台打印一条信息说明:哪个窗口正在为哪种类型的客户服务
System.out.println(windowName+"正在为第"+number+"号"+type+"客户服务");
/*
从这开始
下面的代码是让这个窗口睡眠几秒钟,
下面的代码是让这个窗口睡眠几秒钟,为的是模拟为每个快速客户服务的时间,(即快速客户只能是最小值1秒钟)
* */
long startTime=System.currentTimeMillis();
try {
Thread.sleep(Constants.MIN_SERVICE_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
long costTime=System.currentTimeMillis()-startTime;
到这结束
//下面打印的信息代表的是具体为客户干了些什么事!!
//注意:打印的信息是为哪个窗口为哪种类型的客户服务,并耗时多长
System.out.println(windowName+"为第"+number+"号"+type+"客户完成服务,耗时"+costTime/1000+"秒");
}else{//如果没有叫到快速客户,那么就帮普通窗口客户服务,反正闲着也是闲着
System.out.println(windowName+"没有取到任务!");
commonServiceWindow();
}
}
//vip客户受到服务后具体干了什么事:
private void vipServiceWindow()
{
//定义一个窗口的具体信息名,说明它是VIP窗口
String windowName="第"+windowId+"号"+type+"窗口";
/*
1.在MainTest类的main函数中快速客户已经叫号了,你还不为他服务??
(即Executors.newScheduledThreadPool(1).scheduleAtFixedRate),
2.是个人都知道,银行有个服务机。主要是给客户用的选择办理不同业务用的,
a.可是它也是给窗口的银行服务人员用的啊,他总得知道谁叫了号吧!(即使没有人来,没人叫号)他要先找到服务机吧??
即NumberMachine.getInstance()
b.然后他要知道是哪种类型的客户吧
即NumberMachine.getInstance().getVipManager()
c.再然后他就要获取号了吧
即Integer number=NumberMachine.getInstance().getVipManager().fetchServerNumber();
3.下面就对这个number是否存在进行处理吧!!
* */
Integer number=NumberMachine.getInstance().getVipManager().fetchServerNumber();
//打印一条信息,让你爽下,知道哪中类型的窗口为哪种类型的客户服务
System.out.println(windowName+"正在获取任务");
/*
好吧!服务人员已经取到号了,下面他就要判断这个号是否存在吧,有!就为vip客户服务,
没有!!呵呵呵.....不好意思,为普通客户服务!!!
* */
if(number!=null)
{
//在控制台打印一条信息说明:哪个窗口正在为哪种类型的客户服务
System.out.println(windowName+"正在为第"+number+"号"+type+"客户服务");
/*
从这开始
下面的代码是让这个窗口睡眠几秒钟,
下面的代码是让这个窗口睡眠几秒钟,为的是模拟为每个VIP客户服务的时间(即最大值10秒钟)
* */
long startTime=System.currentTimeMillis();
try {
Thread.sleep(Constants.MAX_SERVICE_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
long costTime=System.currentTimeMillis()-startTime;
到这结束
//下面打印的信息代表的是具体为客户干了些什么事!!
//注意:打印的信息是为哪个窗口为哪种类型的客户服务,并耗时多长
System.out.println(windowName+"为第"+number+"号"+type+"客户完成服务,耗时"+costTime/1000+"秒");
}else{//如果没有叫到VIP客户,那么就帮普通窗口客户服务,反正闲着也是闲着
System.out.println(windowName+"没有取到任务!!");
commonServiceWindow();
}
}
}
4.CustomerType枚举类
系统中有三种类型的客户,所以用定义一个枚举类,其中定义三个成员分别表示三种类型的客户。
重写toString方法,返回类型的中文名称。这是在后面编码时重构出来的,刚开始不用考虑。
看代码:
public enum CustomerType {
//定义三个没见对象,分别代表普通窗口、快速窗口和VIP窗口
COMMON,EXPRESS,VIP;
//对外将每个枚举的实例对象的代表的字符名打印出去
public String toString()
{
switch(this){
case COMMON:
return "普通";
case EXPRESS:
return "快速";
case VIP:
return name();
}
return null;
}
}
6.MainTest类:这是一个测试类,是个入口类,它让整个银行系统跑起来的!!!
具体分析看代码注释:
/**
* 下面是让整个银行系统跑起来的测试类,下面要产生4个普通窗口(1~4),一个快速窗口(即第5号窗口)和一个vip窗口(即第6号窗口)
*/
public static void main(String[] args) {
/*第一步:
你要去一个银行办理业务,总得先有银行存在吧!!也就是说得先有服务窗口存在!
所以第一步是产生6个窗口,1~4号为普通窗口,5号为快速窗口,6号为VIP窗口。
并且不同的窗口是以线程的方式启动,并时时访问服务机(即NumberMachine)是否有要服务的客户:
(即ServiceWindow类中的commonServiceWindow()、expressServiceWindow()、vipServiceWindow()
三个方法中的Integer number=NumberMachine.getInstance().getXXXXXXManager().fetchServerNumber();
)
是否有要被服务的客户得看“第二步”(产生号),没有号你为谁服务啊!!!所以这是下面6个窗口线程是阻塞的,
要有"第二步"的运行才有服务的客户存在
* */
//产生四个普通窗口
for(int i=1;i<5;i++){
ServiceWindow commonWindow=new ServiceWindow();
commonWindow.setWindowId(i);
commonWindow.start();
}
//产生一个快速窗口
ServiceWindow expressWindow=new ServiceWindow();
expressWindow.setWindowId(5);
expressWindow.setType(CustomerType.EXPRESS);
expressWindow.start();
//产生一个vip窗口
ServiceWindow vipWindow=new ServiceWindow();
vipWindow.setWindowId(6);
vipWindow.setType(CustomerType.VIP);
vipWindow.start();
/*第二步:
下面启动三个定时器线程,分别表示多长时间普通客户、快速客户和vip客户来办理业务,按照6:3:1随时产生号码
三种类型的客户都是以定时线程的方式启动,下面三种不同类型的客户按照不同的比例,不停的产生号,这就和上面
"第一步"中6个窗口线程挂起来了,都能跑了!你想啊!!有号产生,窗口又不停的访问服务机是否有号,
这样整个银行系统就跑起来啦!!!
* */
//普通客户来了!!!!来的几率是6,也就是1秒钟来一个
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new Runnable() {
/*
普通客户来了首先干嘛????
当然是到服务机上取号了!!!
1.你要先找到机子吧:NumberMachine.getInstance(),即获得服务机的一个实例对象
2.你要选择哪种类型的客户吧(就是服务机上的各个列表选项),有三种:普通、快速和vip,你是哪种类型客户?就选择哪种啊
好吧!我是普通客户,那就下面:
即NumberMachine.getInstance().getCommonManager();
3.你选择普通客户以后,服务机就会给你张字条,排队的号!!!等着吧,等着那个窗口空闲就叫你
即:Integer number=NumberMachine.getInstance().getCommonManager().generateNewNumber();
*/
@Override
public void run() {
Integer number=NumberMachine.getInstance().getCommonManager().generateNewNumber();
System.out.println(number+"号普通客户来啦!!!等待服务");
}
},
0,
1,
TimeUnit.SECONDS);
//快速客户来了!!!!.来的几率是3,也就是说2秒钟来一个
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new Runnable() {
/*
快速客户来了首先干嘛????
当然是到服务机上取号了!!!
1.你要先找到机子吧:NumberMachine.getInstance(),即获得服务机的一个实例对象
2.你要选择哪种类型的客户吧(就是服务机上的各个列表选项),有三种:普通、快速和vip,你是哪种类型客户?就选择哪种啊
好吧!我是快速客户,来交水电费的,那就下面:
即NumberMachine.getInstance().getExpressManager();
3.你选择快速客户以后,服务机就会给你张字条,排队的号!!!等着吧,没有其他的快速客户就为你服务
即:Integer number=NumberMachine.getInstance().getExpressManager().generateNewNumber();
*/
@Override
public void run() {
Integer number=NumberMachine.getInstance().getExpressManager().generateNewNumber();
System.out.println(number+"号快速客户来啦!!!等待服务");
}
},
0,
2,
TimeUnit.SECONDS);
//VIP客户来了!!!!,来的几率是1,也就是说每个6秒钟来一个
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new Runnable() {
/*
vip客户来了首先干嘛????
当然是到服务机上取号了!!!
1.你要先找到机子吧:NumberMachine.getInstance(),即获得服务机的一个实例对象
2.你要选择哪种类型的客户吧(就是服务机上的各个列表选项),有三种:普通、快速和vip,你是哪种类型客户?就选择哪种啊
废话!我是vip客户,那就请看下面:
即NumberMachine.getInstance().getVipManager();
3.你选择vip客户以后,服务机就会给你张字条,排队的号!!!没有其他的vip就会为你服务了
即:Integer number=NumberMachine.getInstance().getVipManager().generateNewNumber();
*/
@Override
public void run() {
Integer number=NumberMachine.getInstance().getVipManager().generateNewNumber();
System.out.println(number+"号VIP客户来啦!!!等待服务");
}
},
0,
6,
TimeUnit.SECONDS);
}
}
总结:
看了张老师的银行业务调度系统的教学视频,事后自己补充了一点知识,感觉收获最多的是怎么去以”万物皆对象”的思想去看待代码的编程。在这个项目中,只要记住有这几个类:银行里面的服务机(NumberMachine)、服务机里面的三个不同客户的选项(NumberManager)、窗口服务(ServiceWindow)即可。其它的类是辅助这三个对象的。你仔细想想!!你要找银行办理业务是吧!那得有银行吧!在这个项目中就是ServiceWindow这个类;接着你进入银行后要干吗啊???当然到服务机(NumberMachine)那去选择,你要办理的哪种业务吧(NumberManager)!!号是由人产生的,这里我们这接用NumberManager类来模拟产生号的过程,并且是随机的。
有了这三步的思想后面就好办了啊!我们只要在MainTest类中让6个服务窗口跑起来、让产生号的服务机跑起来不就成了!!