------- android培训、java培训、期待与您交流! ----------
项目之银行业务调度系统:
面向对象的设计思路:谁拥有数据,谁就对外提供操作这些数据的方法。
主要需求:
1.有三种对应类型的客户:VIP客户,普通客户,快速客户 ,异步随机生成各种类型的客户,
生成各类型的客户的比例为:1 : 6 : 3。
2.各类型客户在其对应窗口按顺序依次办理业务。
3.当vip窗口和快速业务窗口空闲的时候,可以处理普通业务,
而一旦有对应的客户等待办理业务时,则优先处理对应客户的业务;
4.各服务窗口办理业务的模拟以及时间管理。
关键点:
1.每一个银行的客户相当于银行里的对应一个排队的号码,可以使用号码管理对象对其进行管理,
该号码管理器有一个产生号码和获取号码的功能,因为各个类型的客户是相互独立的,
所以可以在三个独立线程调用三个独立号码管理器对象产生号码的方法,
这样通过控制线程的参数就可以控制各个类型的客户产生的比例了。
另外,这三个号码管理对象通过同一的号码机器来进行管理,该号码机器应用单例设计模式进行定义。
2.每个服务窗口每次找所对应的号码管理器获取当前正排队的客户号码;
3.vip窗口或者快速窗口向与其对应号码管理器要的号码时,预先判断该号码是否为null,是null,就表示该窗口空闲,
然后就调用普通窗口的服务方法;
4.通过线程sleep的方式来模拟并记录办理业务的时间。
思路:
1.定义一个号码管理器类,该类定义一个变量存储上一个客户号码,并定义一个集合存储排队的号码;
2.在该类中向客户提供获取排队号码的方法以及向窗口提供获取被服务号码的方法,注意使用同步避免多线程的安全问题!
3.使用单例设计模式定义一个专门管理号码生成器对象的类,该类私有化构造方法,并通过公共的访问方式提供三个号码管理器对象。
4.在窗口类中定义一个type属性,对应客户类型的三个枚举成员,分别代表普通窗口、快速窗口以及vip窗口。
该类中定义一个获取任务的start()方法,在方法内部启动一个线程,并根据服务窗口的不同,分别调用不同的方法。
为了提高代码的服用性,可以对不同窗口的服务方法进行单独封装,并在循环获取任务时调用。
5.因为客户的类型是固定的,可以使用CustomerType枚举类进行封装,为了测试输出结果更有意义并复写toString()方法。
6.在测试类中,创建普通窗口4个、快速窗口1个、vip窗口1个并调用获取任务的方法。
然后开启三条调度线程产生三种类型的客户,设置好参数,使VIP客户 :普通客户 :快速客户 = 1 :6:3。
public class NumberManager{
private int lastNumber = 1;//定义一个变量用于记录产生的号码,初始化为1
private List<Integer> queueNumber = new ArrayList<Integer>();//定义一个集合用于存储排队的号码
//产生一个新号码 由客户来调用即方法1,注意使用线程同步
public synchronized Integer generateNewManager(){
queueNumber.add(lastNumber);
return lastNumber++;
}
//对外提供获取服务号码的方法2,由窗口来调用
//注意:方法1和方法2是两个线程,操作同一份数据,需要使用同步
public synchronized Integer fetchServiceNumber(){
Integer number = null;
if(queueNumber.size()>0){//先判断是否有值再删除,否则报数组角标越界异常:java.lang.IndexOutOfBoundsException
number = queueNumber.remove(0);
}
//return queueNumber.remove(0);
//先进先出,如果取到null,无法返回成int,会报空指针异常,故使用Integer作为返回值类型
return number;
}
}
//定义一个号码器类,它管理三个号码管理器对象
class NumberMachine{
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;//对外提供公共的访问方式,返回对象,那么外界拿到它的对象后就可以获取以上三个管理器对象了。
}
}
//窗口类
class ServiceWindow{
//定义一个变量,表示该窗口是那种类型的?怎么表示只有三种的窗口呢?用枚举!
private CustomerType type = CustomerType.COMMON;
private int windowId =1;//窗口号
public void start(){
Executors.newSingleThreadExecutor().execute(new Runnable(){
public void run(){
while(true){
/*if(type == CustomerType.COMMON){ --->使用switch语句,比if语句效率高一些
switch(){//注意接收的类型:byte short int char ,枚举(JDK1.5),string(JDK1.7)
NumberMachine.getInstance().getCommonManager().
}
}
else if{}
*/
switch(type){
case COMMON:
/**
String windowName = "第"+ windowId+"号"+type+"窗口";//注意这个窗口类型
//System.out.println(windowName+"正在获取任务");
Integer number = NumberMachine.getInstance().getCommonManager().fetchServiceNumber();//注意该方法时阻塞式的
System.out.println(windowName+"正在获取任务");//如果放在这里将看不到多线成的效果
if(number != null){
long beginTime = System.currentTimeMillis();
int maxRand = Constants.MAX_SERVICE_TIME - Constants.MIN_SERVICE_TIME;
long serveTime = new Random().nextInt(maxServeTime)+1+Constants.MIN_SERVICE_TIME;//1000-10000
try{
Tread.sleep(serveTime);//
}catch(InterruptedException e){
e.printStackTrace();
}
long costTime = System.currentTimeMillis() - beginTime;
System.out.println(windowName+"为第"+number+"个"+”普通“(不能写type,type代表窗口类型)+"客户完成服务,耗时"+costTime/1000);
}else{
System.out.println("没有取到任务,先休息1秒钟!");
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
break;
这些处理代码可以封装起来!*/
commonService();
break;
case EXPRESS:
expressService();
break;
case VIP:
vipService();
break;
}
}
}
});
}
private void commonService(){
String windowName = "第"+ windowId+"号"+type+"窗口";
System.out.println(windowName+"正在获取任务");
Integer number = NumberMachine.getInstance().getCommonManager().fetchServiceNumber();
if(number != null){
long beginTime = System.currentTimeMillis();
int maxRand = Constants.MAX_SERVICE_TIME - Constants.MIN_SERVICE_TIME;
long serveTime = new Random().nextInt(maxServeTime)+1+Constants.MIN_SERVICE_TIME;//1000-10000
try{
Tread.sleep(serveTime);//
}catch(InterruptedException e){
e.printStackTrace();
}
long costTime = System.currentTimeMillis() - beginTime;
System.out.println(windowName+"为第"+number+"个"+type+"客户完成服务,耗时"+costTime/1000);
}else{
System.out.println("没有取到任务,先休息1秒钟!");
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
//枚举,描述客户的类型,固定为三种
public enum CustomerType{
COMMON,EXPRESS,VIP;
public String toString(){ //为了让输出常量的结果更有意义,复写toString方法
switch(this){
case COMMOM:
return "普通";
case EXPRESS:
return "快速";
case VIP:
return "VIP";
}
return null;
}
}
// 将项目中需要用到的常量,单独定义成一个类
class Constants{
public static int MAX_SERVICE_TIME = 10000;
public static int MIN_SERVICE_TIME = 1000;
public static int COMMON_CUSTOMER_INTERVAL_TIME = 1;
}
/* java.util.concurrent.Executors提供了性能更强,功能更强大的操作线程的方法,
在测试类中可以使用开启三条调度线程产生三种类型的客户,设置好参数,普通客户
*/
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable() {
public void run() {
int number = NumberMachine.getInstance().getCommonManager()
.generateNumber();
System.out.println(number + "号普通客户,等待服务");
}
}, 0, Constants.COMMON_CUSTOMER_INTERVAL_TIME, TimeUnit.SECONDS);
总结:
1.面向对象的设计思路:谁拥有数据,谁就对外提供操作这些数据的方法。
该题中,号码管理器类的定义体现了这一思想。该类有生成并存储号码,对外向客户及服务窗口提供获取号码的方法;
所以我们就它封装成一个类进行描述。
2.对于一些固定的常量,如本项目中的客户类型,使用枚举类来描述更好;
3.对于项目中的一些常量,使用单独的类文件来定义;
4.功能的内容差不多的,尽可能进行抽取,提高代码的服用性,如不同类型窗口的服务方式的。
5.JDK1.5新特性的使用,java.util.concurrent.Executors提供了性能更强,功能更强大的操作线程的方法;