首页 > 代码库 > 黑马程序员---银行业务调度系统

黑马程序员---银行业务调度系统

---------------------- Android开发、java培训、期待与您交流! ----------------------

模拟银行业务调度系统

      我们经常到银行取钱存钱时也观察过,在银行一般都有很多的业务窗口,其中有很多普通窗口,它会按顺序叫号,叫到了才可以去办理业务,但是有一些特殊的窗口,叫做理财经业务,我们这里就叫VIP窗口,也就是一些大客户办理业务的窗口,如果让那些大客户也到普通窗口去排队,那些大客户就会不耐烦了,所以对于大客户就单独开了个窗口,就叫做VIP窗口,如果很多大客户去办理业务,那么也要排队,如果大客户不太多,如果大客户忙完了,就空闲了,它也可以为普通窗口叫号。也会有一种窗口叫快速窗口,专门交电话费或者水费的等业务开的。所以我的银行调度系统就设置这三类窗口,分别为三类客户服务。所以我们要异步随机产生这三类客户,按要求这三类客户产生概率比例为:VIP客户 :普通客户 :快速客户 =  1 :6 :3。

由于每个客户办理业务所需要的时间不同,有的客户换个钱就走了,有的客户需要办理个网银或取上万元还在那儿点钱,办理时间长短不同,所以我们就得给办理业务时间设定个最大值和最小时间,对于客户办理业务花费的时间就在最大值和最小值之间的随机值。每类客户到对应窗口办理业务按顺序排列办理。普通客户一般比较多,快速客户和VIP客户一般比较少,所以我们设置普通窗口为4个窗口,快速和VIP窗口分别1一个窗口,共6个窗口,当快速窗口和VIP窗口没有办理的客户时可以为普通客户进行服务。

模拟实现银行业务调度系统逻辑,具体如下:


(1)银行内有6个业务窗口,1- 4号窗口为普通窗口,5号窗口为快速窗口,6号窗口为VIP窗口。

            ---->有三种类型窗口当然就有三中类型的客户了。

(2)有三种对应类型的客户:VIP客户,普通客户,快速客户(办理如交水电费、电话费之类业务的客户)。

            ----->有三种类型的客户当然就得有生成三种类型客户的号码的类和取号机的类。

(3)异步随机生成各种类型的客户,生成各类型用户的概率比例为:

          VIP客户 :普通客户 :快速客户 =  1 :6 :3。

            ----->每一时刻只能产生一类客户的号,普通客户产生的概率占6份,VIP占1份,快速客户占3份,而且每一时刻只能产生一个客户的号,因为每一  银行只有一个取号机,所以取号机类要想设置为单例,而且生成这个号码为了面向对象的封装性也要单独开一个类。

(4)客户办理业务所需时间有最大值和最小值,在该范围内随机设定每个VIP客户以及普通客户办理业务所需的时间,快速客户办理业务所需时间为最小值(提示:办理业务的过程可通过线程Sleep的方式模拟)。

            ----->每种客户办理业务需要时间长短各有不同。利用随机生成器(Random类)产生最大值和最小值之间的时间值,当一个窗口接收到客户,就设置让该窗口线程sleep服务时间长度,在进行获取客户。

(5) 各类型客户在其对应窗口按顺序依次办理业务。

            ----->同一个窗口需要按号码的顺序为一个一个客户的服务,到服务完成之后再为下一个客户。

(6)当VIP(6号)窗口和快速业务(5号)窗口没有客户等待办理业务的时候,这两个窗口可以处理普通客户的业务,而一旦有对应的客户等待办理业务的时候,则优先处理对应客户的业务。

           ----->VIP窗口和快速窗口再没有客户时,可以去为普通客户服务,如果立马就来了VIP客户,VIP则立即转为VIP客户服务。

(7)随机生成客户时间间隔以及业务办理时间最大值和最小值自定,可以设置。

           ----->窗口为每一个客户办理业务的时间随机在最大值和最小值之间,用Random类进行获取随机数。

面向对象设计与分析

(1)有三种对应类型的客户:VIP客户,普通客户,快速客户,异步随机生成各种类型的客户,各类型客户在其对应窗口按顺序依次办理业务
             在银行的每一个客户都是由银行的一个取号机取号产生的,所以想到了有一个号码的管理器对象让这个管理器不断产生号码不就相当于随机生成了客户。

      由于有三类客户,每类客户的号码编排都是完全独立的,所以,我想到本系统一共要产生三个号码管理器对象,各自管理一类用户的排队号码。这三个号码管理器对象统一由一个号码机器进行管理,这个号码机器在整个系统中始终只能有一个,所以,它要被设计成单例。

(2)各类型客户在其对应窗口按顺序依次办理业务,准确地说,应该是窗口依次叫号

              各个窗口要到号码管理器获取该叫那个号了,所以服务窗口找号码管理器就会为窗口生成相应的客户号码。

        所以也必须设置一个类专门为让窗口来利用号码管理器叫取出客户的号码。

**以下是模拟号码管理器类、取号机类、窗口服务类UML类图


 <一>NumberManager
       ①定义一个用于存储上一个客户号码的成员变量和用于存储所有等待服务的客户号码的队列集合。
       ②定义一个产生新号码的方法和获取马上要为之服务的号码的方法,这两个方法被不同的线程操作了相同的数据,所以,要进行同步。

import java.util.ArrayList;
import java.util.List;
/*号码管理器负责号码的生成和获取
*/
public class NumberManager {//号码管理器类
private int lastNumber = 1;//号码管理器生成号码从1开始
private List<Integer> queueNumber = new ArrayList<Integer>();//储存客户号码的集合
public synchronized Integer generateNumber(){//向集合中添加生成的号码,客户去取生成的号码。
queueNumber.add(lastNumber);
return lastNumber++;
}
public synchronized Integer fetchNumber(){//由服务窗口获取号码
if(queueNumber.size()>0)
return queueNumber.remove(0);//返回集合中第一个元素,并从队列中删除第一个号码模拟客户办完业务离开。
else
return null;
}
}

 <二>NumberMachine
       定义三个成员变量分别指向三个NumberManager对象,分别表示普通、快速和VIP客户的号码管理器,定义三个对应的方法来返回这三个NumberManager对象。
       NumberMachine类设计成单例。
/*
 * 负责同一管理三个号码管理器,NumberMachine在程序运行期间只需要一个实例
 *  设计成单例.
 * */
public class NumberMachine {
	// 通过不同的方法获取不同类型的客户队列
    // 这是为了实现不同类型窗口有针对性的为特定类型的客户服务
	private NumberManager CommentManager = new NumberManager();
	private NumberManager ExpressManager = new NumberManager();
	private NumberManager VipManager = new NumberManager();
	//由于该类只有一个实例,因此成员commonManager的值不变
	//也就是说多次调用getCommonManager()获取到的是同一个
	public NumberManager getCommentManager() {
		return CommentManager;
	}
	////NumberManager实例,下面同理
	public NumberManager getExpressManager() {
		return ExpressManager;
	}
	public NumberManager getVipManager() {
		return VipManager;
	}
	 /*NumberMachine在程序运行时在内存限制只有一个实例*/
	private NumberMachine(){}
	private static NumberMachine instance = new NumberMachine();
	public static NumberMachine getInstance(){
		return instance;
	}
}
 <三>CustomerType枚举类
       系统中有三种类型的客户,所以用定义一个枚举类,其中定义三个成员分别表示三种类型的客户。
        ②重写toString方法,返回类型的中文名称。这是在后面编码时重构出来的,刚开始不用考虑。
/**
 * 覆盖toString()打印出客户对应的汉子。
 * */
public enum CustomerType {
	COMMON,EXPRESS,VIP;
	public String toString(){
		switch(this){
		case COMMON:
			return "普通";
		case EXPRESS:
			return "快速";
		case VIP:
			return name();
		}
		return null;
	}
}
  <四>Constants常量
       设置客户办理业务所需时间的最大值和最小值
      ②为没类用户中的每位用户服务的时间间隔。

/**
 * 定义常量的类
 * */
public class Constants {
    //设置客户办理业务所需时间的最大值和最小值
    public final static int MAX_SERVICE_TIME = 10000;
    public final static int MIN_SERVICE_TIME = 1000;
    public final static int COMMON_CUSTOMER_INTERVAL_TIME=1;//每个普通用户来的时间间隔1秒
    public static final int VIP_CUSTOMER_INTERVAL_TIME=COMMON_CUSTOMER_INTERVAL_TIME*6;//每个VIP用户来的时间间隔6秒
    public static final int EXPRESS_CUSTOMER_INTERVAL_TIME=COMMON_CUSTOMER_INTERVAL_TIME*2;//每个快速用户来的时间间隔2秒
}
  <五>ServiceWindow
       定义一个start方法,内部启动一个线程,根据服务窗口的类别分别循环调用三个不同的方法。
       定义三个方法分别对三种客户进行服务,为了观察运行效果,应详细打印出其中的细节信息
import java.util.Random;
import java.util.concurrent.Executors;


public class ServiceWindow {
	private CustomerType type = CustomerType.COMMON;//把三种窗口类型定义成枚举
	private int windowId = 1;//窗口的编号
	//通过set方法设置窗口类型和窗口编号进行设置
	public void setType(CustomerType type) {
		this.type = type;//而没有通过构造方法在创建对象时传入
	}
	//这样做可以修改已创建服务窗口对象的ID和type
	public void setWindowId(int windowId) {
		this.windowId = windowId;
	}
	//相当于启动了NumberMachine,根据服务的不同
	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;
					}
				}
			}
		});
	}
		private void commonService() {
			String windowName = "第"+windowId+"号"+type+"窗口";
			System.out.println(windowName+"正在获取任务");//打印获取服务状态
			Integer number = NumberMachine.getInstance().getCommentManager().fetchNumber();//服务窗口开始拿号,为该号客户提供服务  
			if(number != null){
				System.out.println(windowName+"开始为"+number+"号"+"普通"+"客户服务");//打印普通窗口获取到的普通客户
				int maxRandom = Constants.MAX_SERVICE_TIME - Constants.MIN_SERVICE_TIME;
				int serviceTime = new Random().nextInt(maxRandom)+1+Constants.MIN_SERVICE_TIME; //产生随机的服务时间
				System.out.println(windowName + "完成为第" + number + "号"+"普通"+"客户服务,总共耗时" + serviceTime/1000 + "秒");	//打印完成服务的记录。
			}else{
				System.out.println(windowName+"没有取到任务,先休息一秒!");//打印没有获取到服务,窗口的状态。
				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().fetchNumber();//服务窗口开始拿号,为该号客户提供服务
			if(number !=null){
				System.out.println(windowName + "开始为第" + number + "号快速客户服务");	//打印快速窗口获取到的快速客户		
				int serviceTime = Constants.MIN_SERVICE_TIME;//线程休眠的时间为最小时间
				try {
					Thread.sleep(serviceTime);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				//完成服务的记录。	
				System.out.println(windowName + "完成为第" + number + "号快速客户服务,总共耗时" + serviceTime/1000 + "秒");		
			}else{
				System.out.println(windowName + "没有取到快速任务!");//打印没有获取到服务,窗口的状态。			
				commonService();//如果没有快速客户就接待普通客户。
			}
		}
		private void VipService(){
			String windowName = "第"+windowId+"号"+type+"窗口";
			System.out.println(windowName+"正在获取任务");//打印获取服务状态
			Integer number = NumberMachine.getInstance().getVipManager().fetchNumber();//服务窗口开始拿号,为该号客户提供服务
			if(number !=null){
				System.out.println(windowName + "开始为第" + number + "号VIP客户服务");	//打印VIP窗口获取到的VIP客户		
				int serviceTime = Constants.MIN_SERVICE_TIME;//线程休眠时间为最长时间。
				try {
					Thread.sleep(serviceTime);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				//完成服务的记录。			
				System.out.println(windowName + "完成为第" + number + "号VIP客户服务,总共耗时" + serviceTime/1000 + "秒");		
			}else{
				System.out.println(windowName + "没有取到VIP任务!");//打印没有获取到服务,窗口的状态。				
				commonService();//没有VIP客户就接待普通客户。
			}
		}
}
   <六>主函数
         用for循环创建出4个普通窗口,再创建出1个快速窗口和一个VIP窗口。
         接着再创建三个定时器,分别定时去创建新的普通客户号码、新的快速客户号码、新的VIP客户号码。
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class MainClass {

	/**
	 * 
	 */
	public static void main(String[] args) {
/**
 * 生成服务窗口
 * 产生每种窗口的种类及其数量。
 */
		//普通窗口
		for(int i=1;i<5;i++){
			ServiceWindow commonWindow = new ServiceWindow();
			commonWindow.setWindowId(i);
			commonWindow.start();
		}
		//快速窗口
		ServiceWindow expressWindow = new ServiceWindow();
		expressWindow.setType(CustomerType.EXPRESS);
		expressWindow.start();
		//VIP窗口
		ServiceWindow VipWindow = new ServiceWindow();
		VipWindow.setType(CustomerType.VIP);
		VipWindow.start();
/**
 * 产生客户号
 * 创建线程池,每一个线程池中只允许运行一个线程。每种客户在同一个线程池里。
 * 通过控制线程之间的周期时间而再到产生每种客户的要求比例。
 * */		
		//普通客户拿号
		Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
				new Runnable(){

					@Override
					public void run() {
						Integer ServiceNumber = NumberMachine.getInstance().getCommentManager().generateNumber();
						System.out.println("第"+ServiceNumber+"普通客户正在等待服务");
					}
					}, 
					0, 
					Constants.COMMON_CUSTOMER_INTERVAL_TIME,
					TimeUnit.SECONDS
					);
		//快速客户拿号
		Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
				new Runnable(){

					@Override
					public void run() {
						Integer ServiceNumber = NumberMachine.getInstance().getExpressManager().generateNumber();
						System.out.println("第"+ServiceNumber+"快速客户正在等待服务");
						
				}},
				0,
				Constants.EXPRESS_CUSTOMER_INTERVAL_TIME,
				TimeUnit.SECONDS
				);
		//VIP客户拿号
		Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
				new Runnable(){

					@Override
					public void run() {
						Integer ServiceNumber = NumberMachine.getInstance().getVipManager().generateNumber();
						System.out.println("第"+ServiceNumber+"VIP客户正在等待服务");
						
			}},
			0, 
			Constants.VIP_CUSTOMER_INTERVAL_TIME, 
			TimeUnit.SECONDS
			);
	}

}

【总结】

模拟银行业务调度系统的程序充分对Java面向对象灵活运用的一个考验,包含了代码的封装,如何抽取共性代码,多线程,单例设计模式,枚举类等Java的知识点,可见要写一些程序Java基础是非常的重要。编写模拟银行业务调度系统代码前要考虑客户的产生到窗口的叫号再到服务完成的全过程都需要哪些事情。客户的产生就需要号码管理器类(NumberManager类)进行管理号码和取号机类(NumberMachine类)进行取出号码当服务窗口叫到客户进行服务有要考虑到服务多长时间,窗口没取到客户又是如何的动作,所以又得创建一个服务窗口类(SeriviceWindow类),过来就是主函数类,要产生窗口,进行调用取号机类方法进行取号操作。最好有一些常量需要定义一个常量类进行存放,还有利用枚举类定义这三种类型的客户或窗口的名称,并覆盖toString()方法给予中午打印。这就是我对模拟银行业务调度系统大致的理解描述,真正启发我的是面向对象的操作。



---------------------- Android开发、java培训、期待与您交流! ----------------------详细请查看:www.itheima.com