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

来源:互联网 发布:天生丽质难自弃网络语 编辑:程序博客网 时间:2024/05/16 17:07

------- android培训java培训、期待与您交流! -------

一、现实中银行业务流程:

1、假设银行有3种对应的客户:普通客户、快速用户、VIP用户。

2、当3种用户进入银行后,他们会在同一个取号机上进行取号,但是他们取的编号的类型不同。普通用户取号依次是:普通用户1号编号、普通用户2号编号、普通用户3号编号... …

快速用户取号编号依次是:快速用户1号编号、快速用户2号编号、快速用户3号编号… …

VIP用户取号编号依次是:VIP用户1号编号、VIP用户2号编号、VIP用户2号编号… …

3、银行中有对应的服务窗口,因为有三种不同的用户,所以需要有3中不同的服务窗口,每种服务窗口向对应的用户(或者说用户编号)提供服务,窗口会对用户从取号机取走编号的先后顺序进行服务。当服务窗口没有获取正在等待的用户,窗口出于闲置状态。

4、银行还有这样一种机制:当普通用户过多,有普通用户等待的时候,如果快速窗口或者VIP窗口处于闲置状态时,这些窗口会临时对普通用户进行服务。

由以上业务逻辑的分析,我们就可以用面向对象的思想对银行业务调度系统进行分析与设计了。

二、银行业务调度系统面向对象分析:

1、银行要有一个取号机:NumberMachine,并且只有一个,所以我们把NumberMachine设计为单例,取号机上有3种编号管理机制,分别是:普通用户编号管理器:commonManager、

快速用户编号管理器:expressManager、VIP用户编号管理器:VIPManager。所以这三个对象以成员数据的形式存在于NumberMachine类中,当然还要有获取这些成员的方法。

NumberMachine类代码示例及详细注释:

package com.itheima.test.bank;//编号机器:因为只有一个编号机器,所以设计成单例。public class NumberMachine {//普通用户编号管理器private NumberManager commonManager = new NumberManager();//快速用户编号管理器private NumberManager expressManager = new NumberManager();//VIP用户编号管理器private NumberManager VIPManager = new NumberManager();//机器获取Common用户编号管理器public NumberManager getCommonManager() {return commonManager;}//机器获取Express用户编号管理器public NumberManager getExpressManager() {return expressManager;}//机器获取VIP用户编号管理器public NumberManager getVIPManager() {return VIPManager;}//把构造函数私有化,在类外不能被创建对象private NumberMachine(){}//在类内自定义一个对象private static NumberMachine instance = new NumberMachine();//对外提供一个public方法,用于获取这个对象:NumberMachine.getInstance()public static NumberMachine getInstance(){return instance;}}

2、同样需要创建编号管理器的类:NumberManager,因为系统描述的是某一个编号被服务的过程,只需要以字符串的形式打印出来即可,所以就不需要创建客户对象了。因此编号管理器需要有添加编号的功能,同时也要有取走编号的功能。这时我们就想想到了集合,同时也要考虑到编号自增的过程。并且NumberManager添加的编号,正是ServiceWindow要进行服务的编号。

NumberManager类代码示例及详细注释:

package com.itheima.test.bank;import java.util.ArrayList;import java.util.List;//编号管理器:生成编号和提供需要被服务的编号public class NumberManager {//定义一个int变量表示:上次的编号。初始化值是1编号private int lastNumber = 1;//定义一个编号队列集合,用于存储从1~n的编号private List<Integer> queueNumber = new ArrayList<Integer>();//号码管理器:生成新的编号,编号从1开始++public synchronized Integer generateNewNumber(){//生成新的编号,并且添加到编号队列中queueNumber.add(lastNumber);//返回当前编号++return lastNumber++;}//号码管理器:提供需要被服务的客户号码:号码由编号表示//每添加进去 一个编号,就移除一个编号。并且,这两个动作必须是一次性完成,所以要加上同步锁。public synchronized Integer fetchServiceNumber(){Integer number = null;if(queueNumber.size() > 0){return queueNumber.remove(0);}return number;}}

3、虽然不需要有客户对象,但是必须要对客户类型进行描述,因为不同类型的客户他们接受的服务时不同的,所以我们需要创建一个客户类型的类:CustomerType。因为客户类型固定,个数又是固定,所以考虑到用枚举比较方便,同时重写toString()方法返回枚举值的中文表示。

CustomerType类代码示例:

package com.itheima.test.bank;//创建一个枚举,因为客户类型只有三个。public enum CustomerType {COMMON,EXPRESS,VIP;//重写toString()方法,把客户类型打印成中文表示形式。public String toString(){switch(this){case COMMON:return "普通";case EXPRESS:return "快速";case VIP:return name();//name()方法返回枚举类实例对象,此处返回VIP}return null;}}

4、服务窗口是该系统中显而易见的对象,所以要创建一个服务窗口类:ServiceWindow。服务窗口包括哪些数据呢?服务窗口需要对不同类型的客户进行服务,所以服务窗口需要有客户类型(CustomerType)这一成员数据,其次服务窗口还有不同的窗口号(普通用户服务窗口有4个,只有他有编号),所以窗口号(windowId)也是ServiceWindow类的成员数据,当然谁拥有数据,谁就有操作这些数据的方法。接下来服务窗口就要执行任务了,我们把要执行的任务放在了一个线程池中,让线程池在内部找一个空闲的线程来执行命令。因为不同的服务窗口服务的客户类型不同,所以他们执行的任务细节也不尽相同。因此ServiceWindow在执行任务时需要对客户类型进行判断,执行不同的具体细节。例如,普通用户服务窗口对普通用户进行服务,下边我们就详细分析一个普通用户服务窗口具体是如何服务的。需要注意的是:窗口对客户服务的动作是循环的。

         普通用户服务窗口刚启动时,就开始准备获取任务,这时需要判断一下是不是存在编号了?如果存在的话,就对该编号进行服务,并且可以使用Random随机数和sleep()等方式对服务客户的时间进行控制;如果此刻没有服务编号,那就用sleep()方法让该任务休息一会。就这样循环的判断和执行。

         快速用户服务窗口对快速用户服务,VIP用户窗口对VIP用户进行服务,这两个执行过程与普通用户服务窗口服务过程大致是一样的,但是快速用户窗口、VIP用户窗口还有一个特殊的机制,就是当普通用户过多,没有闲置普通窗口对其服务而处于等待中的时候,快速用户窗口、VIP用户窗口没有任务需要执行时,可以临时对普通用户进行服务。

         在编写窗口执行任务的代码中,要适时打印出详细的细节,来呈现处对用户进行服务的过程。

ServiceWindow类代码示例及详细注释:

package com.itheima.test.bank;import java.util.Random;import java.util.concurrent.Executors;//创建一个服务窗口类public class ServiceWindow {//设置客户类型的初始值为COMMONprivate CustomerType type = CustomerType.COMMON;//设置窗口号的初始值为1private int windowId = 1;//设置客户类型的方法public void setType(CustomerType type) {this.type = type;}//设置窗口号的方法,因为只有Common用户服务有多个窗口,所以只有普通窗口才调用该方法。public void setWindowId(int windowId) {this.windowId = windowId;}//服务窗口开始启动任务public void start(){//把任务交给线程池,而不是交给某一个线程,线程池在内部找一个空闲的线程来执行命令。Executors.newSingleThreadExecutor().execute(new Runnable(){public void run(){while(true){//用if else方式,不但代码可读性差,而且效率没有switch高。switch(type){case COMMON:commonService();//如果客户类型是COMMON,就开始普通服务break;case EXPRESS:expressService();//如果客户类型是EXPRESS,就开始快速服务break;case VIP:VIPService();//如果客户类型是VIP,就开始VIP服务break;}}}});}//普通用户窗口服务过程private void commonService() {//定义一个当前窗口名,String windowName = "第" + windowId + "号" + type + "窗口";//定义一个窗口从普通用户管理器获取到要服务的编号Integer number = NumberMachine.getInstance().getCommonManager().fetchServiceNumber();//打印windowName正在试图获取任务,模拟窗口检查是否有编号等待。System.out.println(windowName + "正在获取任务");//判断有编号等待if(number != null){//小细节:此处必须硬编码打印"普通",因为可能快速或者VIP窗口,调用该方法为普通客户服务。System.out.println(windowName + "正在为第" + number + "个" + "普通" + "客户服务");//定义一个开始服务的时间点long beginTime = System.currentTimeMillis();//定义一个最长时间与最短时间的差值int maxRand = Constants.MAX_SERVICE_TIME - Constants.MIN_SERVICE_TIME;//定义一个随机值,随机值的最小值是1000,最大值是10000long serverTime = new Random().nextInt(maxRand + 1) + Constants.MIN_SERVICE_TIME;try {//用线程sleep的方式,模拟服务持续的随机时间,范围是(1~10秒)Thread.sleep(serverTime);} catch (InterruptedException e) {e.printStackTrace();}long costTime = System.currentTimeMillis() - beginTime;//打印服务耗时System.out.println(windowName + "为第" + number + "个" + "普通" + "客户完成服务,耗时:" + costTime/1000 + "秒");}else{//如果没有编号在等待,打印窗口休息1秒。System.out.println("没有取到服务任务,先休息1秒钟吧!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}//快速用户窗口服务过程private void expressService() {String windowName = "第" + windowId + "号" + type + "窗口";Integer number = NumberMachine.getInstance().getExpressManager().fetchServiceNumber();System.out.println(windowName + "正在获取任务");if(number != null){System.out.println(windowName + "正在为第" + number + "个" + type + "客户服务");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 + "没有取到服务任务!");//如果快速窗口没有相应编号等待,那么它临时为普通用户服务commonService();}}//VIP用户窗口服务过程private void VIPService() {//定义一个窗口名String windowName = "第" + windowId + "号" + type + "窗口";Integer number = NumberMachine.getInstance().getVIPManager().fetchServiceNumber();System.out.println(windowName + "正在获取任务");if(number != null){System.out.println(windowName + "正在为第" + number + "个" + type + "客户服务");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 + "没有取到服务任务!");//如果VIP窗口没有相应编号等待,那么它临时为普通用户服务commonService();}}}

5、因为在程序设计中,多处用到了一些固定的值,为了增强程序的扩展性和可读性,用定义了一个常量类:Constants类。包括:最短执行时间:MIN_SERVICE_TIME,最长执行时间:MAX_SERVICE_TIME,执行命令的时间间隔:COMMON_CUSTOMER_INTERVAL_TIME。
Constants类的代码示例:

package com.itheima.test.bank;//创建一个常量类public class Constants {//定义一个最短服务时间的常量public static int MIN_SERVICE_TIME = 1000;//定义一个最长服务时间的常量public static int MAX_SERVICE_TIME = 10000;//定义一个命令执行的时间间隔,或者说两个Common客户来到银行的的时间间隔public static int COMMON_CUSTOMER_INTERVAL_TIME = 1;}

6、主类:MainClass。就是要让银行系统运作起来,那么就需要创建服务窗口了。根据系统需求,需要创建4个普通用户窗口,1个快速用户窗口,1个VIP用户窗口。然后,就需要设置窗口对应的要服务的客户类型了,创建ServiceWindow类时,初始化的客户类型是COMMON。最后就start()执行任务了。但是还没有客户怎么进行服务呢?接下类就要分别创建线程池,执行添加客户的命令了(实际是往集合中添加客户编号)。系统需求生成各类用户的比例是VIP客户:普通客户:快速客户 = 1:6:3,所以他们各自线程池中执行命令(添加客户)的时间间隔分别是:6秒、1秒、2秒。

MainClass类的代码示例及详细注释:

package com.itheima.test.bank;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;public class MainClass {public static void main(String[] args) {//创建5个Common用户服务窗口,因为有5个窗口,所以要设置窗口号for(int i=1;i<5;i++){ServiceWindow commonWindow = new ServiceWindow();commonWindow.setWindowId(i);//Common窗口开始启动服务commonWindow.start();}//创建1个Express用户服务窗口,无需设置窗口号ServiceWindow expressWindow = new ServiceWindow();//设置客户类型为:快速客户expressWindow.setType(CustomerType.EXPRESS);//Express窗口开始启动服务expressWindow.start();//创建1个VIP用户服务窗口,无需设置窗口号ServiceWindow VIPWindow = new ServiceWindow();//设置客户类型为:VIP客户VIPWindow.setType(CustomerType.VIP);//VIP窗口开始启动服务VIPWindow.start();//创建一个线程数为1的线程池,执行向普通用户管理器的集合中中添加元素的过程,模拟一个接一个普通用户来到银行。Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable(){public void run(){//最初,1号普通客户加进集合中,模拟第一个人来到银行。Integer number = NumberMachine.getInstance().getCommonManager().generateNewNumber();//打印出:1号普通客户等待你服务!等待commonWindow.start();中的程序响应。System.out.println(number + "号普通客户等待你服务!" );}}, 0, //命令不需要延迟Constants.COMMON_CUSTOMER_INTERVAL_TIME, //每隔1秒重复执行命令,1,2,3...n号普通客户来到。TimeUnit.SECONDS);//每隔2秒进来一位快速客户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 * 2, TimeUnit.SECONDS);//每隔6秒进来一位VIP客户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);}}

为避免误导初学者,本博客如有错误或者不严谨的地方,请在下方给予评论,以便及时修改!谢谢... ...

-------------------------android培训java培训、期待与您交流! ----------------------

详细请查看:www.itheima.com

0 0