最近两天看了张孝祥老师讲解的银行业务调度系统,完成了自己的第一个java小程序。需求分析如下:
需求分析
结合实际在银行办理业务的背景可以知道,每一个客户实际就是由银行的一个取号机器产生号码的方式来表示的。因此系统需要有一个号码管理器对象,这个对象不断地产生号码,就等于生成了客户。由于有三类客户,每类客户的号码编排都是完全独立的,所以系统中共要产生3个号码管理器,各自管理自己一类用户的排队号码。这三个号码管理器对象统一由一个号码机器管理,因此系统中还需要产生一个号码机器对象。
各类型的客户在对应窗口按顺序依次办理业务,准确的说,应该是窗口依次叫号,即服务窗口每次找号码管理器获取当前要被服务的客户的号码。
类的设计
根据前面的分析可知系统需要的类有:
(1) NumberManager,负责产生新号码和为服务窗口提供下一个需要被服务的客户号码。号码存储采用容器ArrayList,产生的新号码加在队尾,取号从队首取。因为服务窗口有多个,因此取号操作会采用多线程,因此这里提供号码的方法应标记为synchronized。
package edu.uestc.bank;
import java.util.ArrayList;
import java.util.List;
public class NumberManager {
//变量类型尽量用超类,这样以后重构会更灵活
//private ArrayList<Integer> numList;
private List<Integer> numList;
private int lastNumber;
public NumberManager() {
lastNumber = 0;
numList = new ArrayList<Integer>();
}
//产生新号码
public void generateNumber() {
numList.add(++lastNumber);
}
//若numList不为空,则取第一个号码;
//否则返回-1
public synchronized int fetchNumber() {
if(!numList.isEmpty()) {
return numList.remove(0);
}
else
return -1;
}
}
(2) NumberMachine, 它包含三个号码管理器,以及获取这三个号码管理器的方法。(因为系统中只需要一个号码机器,产生多好NumberMachine的实例是没有意义的,因此这里最好将该类设计为单例类,具体设计见版本2)
package edu.uestc.bank;
public class NumberMachine {
private NumberManager commonManager;
private NumberManager vipManager;
private NumberManager expressManager;
public NumberMachine(NumberManager commonManager, NumberManager vipManager, NumberManager expressManager) {
this.commonManager = commonManager;
this.vipManager = vipManager;
this.expressManager = expressManager;
}
public NumberManager getCommonManager() {
return commonManager;
}
public NumberManager getVipManager() {
return vipManager;
}
public NumberManager getExpressManager() {
return expressManager;
}
}
(3) ServiceWindow, 负责根据窗口的类型(普通,快速,Vip)提供相应的服务。因为多个窗口应可以同时提供服务,所以窗口提供服务应该是多线程的,因此这里让ServiceWindow继承了Thread类,并重写run方法:使窗口可以根据自己的类型完成对应的操作。因此该类的属性应该包括窗口号码windowNum,窗口类型windoType;又窗口需要向号码管理器要号码,因此还应有属性NumberMachine numMach。此外还有为获取随机服务时间引入的几个属性。窗口可以提供commonService( ) 、expressService( )、vipService( )几种服务,由run方法根据窗口类型进行相应的调用。(需要几点改进:第一可以采用线程池提高运行效率;第二run方法调用不同类型服务时用switch比用else if效率高;第三因为快速服务窗口和vip服务窗口也可以提供普通服务,因此可以使commonService方法得到重用,具体见版本2 )
package edu.uestc.bank;
import java.util.Random;
public class ServiceWindow extends Thread {
private static int id = 0;
private int windowNum ;
private long ramServTime;
private int windowType; //1:普通客户,2:快速客户,3:Vip客户
private static long maxServTime = 10000;
private static long minServTime = 1000;
private boolean flag = true;
private NumberMachine numMach;
public ServiceWindow(int windowType, NumberMachine numMach) {
windowNum = ++id;
this.windowType = windowType;
this.numMach = numMach;
}
public long getRamServTime() {
ramServTime = minServTime + (new Random()).nextInt((int)(maxServTime - minServTime));
return ramServTime;
}
public void run() {
while(flag) {
if(windowType == 1) {
commonService();
}
else if(windowType == 2) {
expressService();
}
else if(windowType == 3) {
vipService();
}
else {
System.out.println("wrong windowType!");
}
}
}
public void stopService() {
flag = false;
}
public void commonService() {
System.out.println(windowNum + "号普通窗口正在取普通号");
int num = numMach.getCommonManager().fetchNumber();
if( -1 == num) {
System.out.println("没有普通客户正在等待" + windowNum + "号普通窗口空闲1秒");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else {
System.out.println(windowNum + "号普通窗口正在为" + num + "号普通客户服务");
long servTime = getRamServTime();
try {
Thread.sleep(servTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(windowNum + "号普通窗口为" + num + "号普通客户服务结束,服务时间为" + servTime + "毫秒");
}
}
public void vipService() {
System.out.println(windowNum + "号Vip窗口正在取Vip号");
int num = numMach.getVipManager().fetchNumber();
if(-1 == num) {
System.out.println("没有Vip客户正在等待");
System.out.println(windowNum + "号Vip窗口正在取普通号");
int comNum = numMach.getCommonManager().fetchNumber();
if(comNum == -1) {
System.out.println("没有普通客户正在等待" + windowNum + "号vip窗口空闲1秒");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else {
System.out.println(windowNum + "号vip窗口正在为" + comNum + "号普通客户服务");
long servTime = getRamServTime();
try {
Thread.sleep(servTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(windowNum + "号vip窗口为" + comNum + "号普通客户服务结束,服务时间为" + servTime + "毫秒");
}
}
else {
System.out.println(windowNum + "号Vip窗口正在为" + num + "号Vip客户服务" );
long servTime = getRamServTime();
try {
Thread.sleep(servTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(windowNum + "号vip窗口为" + num + "号Vip客户服务结束,服务时间为" + servTime + "毫秒");
}
}
public void expressService() {
System.out.println(windowNum + "号快速窗口正在取快速号");
int num = numMach.getExpressManager().fetchNumber();
if(-1 == num) {
System.out.println("没有快速客户正在等待");
System.out.println(windowNum + "号快速窗口正在取普通号");
int comNum = numMach.getCommonManager().fetchNumber();
if(comNum == -1) {
System.out.println("没有普通客户正在等待" + windowNum + "号快速窗口空闲1秒");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else {
System.out.println(windowNum + "号快速窗口正在为" + comNum + "号普通客户服务");
long servTime = getRamServTime();
try {
Thread.sleep(servTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(windowNum + "号快速窗口为" + comNum + "号普通客户服务结束,服务时间为" + servTime + "毫秒");
}
}
else {
System.out.println(windowNum + "号快速窗口正在为" + num + "号快速客户服务" );
try {
Thread.sleep(minServTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(windowNum + "号快速窗口为" + num + "号快速客户服务结束,服务时间为" + minServTime + "毫秒");
}
}
}
(4) GenerateCustomer, 负责按要求的比例产生新号码。为了实现不同类型客户数量之间的比例,采用了Random类的nextInt( )方法。( 但是这种方法不是最恰当的,因为需求里面要求“异步产生”。在版本2会采用一种更合适的方法:使用调度线程池ScheduledExectorService的按固定速率执行方法scheduleAtFixedRate( ) )
package edu.uestc.bank;
import java.util.Random;
public class GenerateCustomer extends Thread {
private boolean flag = true;
private Random ram = new Random();
private NumberMachine numMach;
public GenerateCustomer( NumberMachine numMach) {
this.numMach = numMach;
}
public void run() {
while(flag) {
int temp = ram.nextInt(10);
if(temp >= 0 && temp <= 5) {
numMach.getCommonManager().generateNumber();
}
else if(temp >= 6 && temp <=8) {
numMach.getExpressManager().generateNumber();
}
else {
numMach.getVipManager().generateNumber();
}
try {
Thread.sleep((long)(Math.random() * 100));
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
public void stopGenerate() {
flag = false;
}
}
(5) MainClass,负责使系统运行起来,启动生成号码、提供服务线程,经过一定时间后停止运行。
package edu.uestc.bank;
public class MainClass {
public static void main(String[] args) {
NumberManager commonManager = new NumberManager();
NumberManager vipManager = new NumberManager();
NumberManager expressManager = new NumberManager();
NumberMachine numMach = new NumberMachine(commonManager, vipManager, expressManager);
ServiceWindow serv1 = new ServiceWindow(1, numMach); //普通窗口
ServiceWindow serv2 = new ServiceWindow(1, numMach);
ServiceWindow serv3 = new ServiceWindow(1, numMach);
ServiceWindow serv4 = new ServiceWindow(1, numMach);
ServiceWindow serv5 = new ServiceWindow(2, numMach); //快速窗口
ServiceWindow serv6 = new ServiceWindow(3, numMach); //vip窗口
GenerateCustomer generateCustomer = new GenerateCustomer(numMach);
generateCustomer.start();
serv1.start();
serv2.start();
serv3.start();
serv4.start();
serv5.start();
serv6.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
generateCustomer.stopGenerate();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
serv1.stopService();
serv2.stopService();
serv3.stopService();
serv4.stopService();
serv5.stopService();
serv6.stopService();
}
}
完成。