黑马程序员——银行业务调度系统笔记

来源:互联网 发布:mvp 网络请求 编辑:程序博客网 时间:2024/05/16 15:29

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

 

以下内容为学习张孝祥银行业务调度系统视频教程时所做的笔记

网页显示版面错乱 下载原笔记文件

银行业务调度系统

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

Ø  不要求实现GUI,只考虑系统逻辑实现,可通过Log方式展现程序运行结果。

 

1、对象分析与设计:

       并不是随便谁进到银行里就是客户了,有些进去转一圈又出去的不能算银行客户,怎么区分呢?到银行里取号的才是客户,不取号的就不是。所以用一个号码管理器(排队机)产生号码的方式来表示银行客户的产生。需要一个号码管理器对象。

       由于有三种类型的客户,三类客户排号彼此独立,不能来个普通客户取1号后,再来一个VIP成2号了。所以要有3个独立的号码管理器来管理三种类型客户的排队问题。这三个号码管理器通过一个排队机统一管理。排队机在整个银行系统中只有一个,所以要设计成单例。

       各类客户在对应窗口依次办理业务,用程序设计方法具体怎么理解呢?客户拿到号就跑到窗口去办理业务吗?NO,是业务窗口叫号,叫到了你的号,你才能去对应窗口办理。窗口怎么知道该叫哪一个号呢?当然是问与其对应的号码管理器了。业务窗口每办理一次业务都要从对应的号码管理器获取将要服务的客户对象。

 

2、各对象之间的关系:

       号码机器管理三个号码管理器,应该提供获取这三个管理器对象的方法;因为号码机器是单例的,所以要提供一个返回实例对象的静态方法。

       各个号码管理器要根据客户类型相应的产生新客户号码,还要响应窗口的服务请求(窗口开始服务,要向号码管理器要客户号码)。

业务窗口开始服务时要获取与其对应的客户号码,就需要请求号码机器,号码机器根据窗口类型返回与之对应的号码管理器,窗口再从号码管理器中获取要服务的客户号码,完成业务办理。

VIP窗口和快速窗口还要提供普通客户服务。

张老师画的类图:

 

3、号码管理器的实现:

class NumberManager

{

       c、添加成员变量,上一次产生的号码初始号码为1

       private int lastNum = 1;

       e、有多个客户就要排队,需要存放客户

       private List<Integer> consumerQueue = new ArrayList<Integer>J();

       a、号码管理器需要为客户产生新号码

       public synchronized int Integer generateNewNumber()

       {

              f、客户每取走一个号,就将号码存入客户队列中

              consumerQueue.add(lastNum);

b、每次产生的号码都是上一次号码加

还需要一个成员变量表示上次产生的号码

  return lastNum++;

}

d、号码管理器还要响应业务窗口的请求,为窗口安排服务对象

  返回客户队列中的第一个,又需要一个集合存放排队的客户

public int Integer synchronized fetchServiceNumber()

{

  g、窗口空了,需要服务对象,返回客户队列中的第一个

  return consumerQueue.remove(0);

h、因为号码管理器产生号码、业务窗口请求服务号码是在不同的线程中,都要操作客户队列,会产生线程不同步的问题,所以要用把这两个方法用synchronized进行修饰,使使用这两个方法的线程互斥,保证同步

}

}

 

4、号码机器的实现:管理三个号码管理器

class NumberMachine

{

a、统管这三个号码管理器,需要三个成员,指向三个号码管理器

private NumberManager commonManager = new NumberManager();

private NumberManager expressManager = new NumberManager();

private NumberManager VIPManager = new NumberManager();

 

b、窗口请求号码时,需要获取对应的号码管理器,所以需要对应的三个获取号码管理器的方法,其他两个方法一样,就不写了

public NumberManager getCommonManager()

{

  return commonManager;

}

       c、号码机器只有一个,需要将其单例化,构造方法私有即可

       private NumberMachine(){};

       d、构造方法私有了,外部就不能创建对象了,需要提供获取对象的方法,静态修饰,不用创建对象即可调用。方法名用getInstance()好些

       public static NumberMachine getNumberMachine()

       {

              我当时就这样写的,张老师是在方法外部创建一个对象,用这个方法返回外部创建好的静态实例对象,内部返回的不是静态的不知道行不行,写代码是验证吧 instance

  return new NumberMachine();

}

}

 

5、服务窗口类的分析及骨架代码:ServiceWindow

窗口要提供服务就要到排队机中获取客户号码,窗口叫号方法属于内部方法,系统启动后,窗口就开始不停叫号,有客户号码就开始服务,所以还需要一个单独的线程,创建线程可以用JDK1.5提供的新方法  Executors

使用Executors创建线程池,将运行任务交给线程池,线程池执行任务时从池子中寻找一个空闲的线程来执行任务。

e、表示窗口类别的枚举 客户类型与窗口类型对应customerType

enum CustomerType

{

       COMMON,EXPRESS,VIP;

       k、复写toString方法,返回需要的中文

       public String toString()

       {

  switch(this)

  {

case COMMON: return “普通”;

case EXPRESS: return “快速”;

case VIP: return name()或者“VIP”;枚举的name()方法就是返回对象的名字

}

此处还要一个返回语句,因为编译器不知道上边的选择中有没有结果返回,而该方法需要一个返回值,所以这里再return一下

return null;

}

}

class ServiceWindow

{

d、需要指定窗口类型,可以用数字指定,但窗口只有三种,用枚举更专业,所以枚举吧private int type;默认为普通窗口

       private CustomerType type = CustomerType.COMMON;

       h2、定义窗口号

       private int windowId = 1;

iVIP和快速窗口可以变为普通窗口,所以还需要提供改变类型和窗口号码的方法

public void setType(CustomerType type)

{this.type = type;}

public void setWindowId(int windowId)

{this.windowId = windowId;}

a、窗口开始提供服务

public void start()

{

  b、开始服务就不停的叫号

  Executors.newSingleThreadExecutor()..executor(

new Runnable() 线程运行的任务

{

       public void run()

       {

              c、窗口一开始服务就需要不停地向排队机要号,根据自己的窗口类型得到一个对应的号码管理器,需要一个表示窗口类型的变量

            NumberManager.getInstance().getCommonManager();

f、上面就需要判断了,根据类型获取管理器

if (type==CustomerType.CMMON)

else if()……

else ……if else没有switch效率高

switch只能用int和枚举进行比较

                           改为switch 可以判断枚举

                            switch(type)

                            {可以省略枚举前缀

                            case CustomerType.COMMON:

                            h1、哪一个窗口正在获取任务呢,第几号呢

普通窗口有4个,需要定义一个窗口号码

                           j、获取窗口类型和号码加在(h1)下边的输出语句中

                           type是英文可以在枚举中复写toString方法返回中文

                     String windowName = 第+windowId+号+type +窗口;

                                   SOP(windowName+正在获取任务);

g、获取CommonManager

                            获取号码管理器后,用它拿到客户号码 

                           此处注意:刚开始客户队列集合中都是Integer对象,而返回的号码是int,由于JDK1.5增加了自动拆装箱功能,所以返回时不会有问题。但这里就有可能出问题,当没有客户时,返回的是null,自动拆装箱不能将null拆成int呀,所以这里用Integer接收返回的对象后面再进行判断

                     int Integer num = NumberMachine.getInstance().getCommonManager().fetchServiceNumber();

                            if  (num!=null)

                            else SOP(没有获取到任务);

break;

                            case EXPRESS:

获取号码控制器break;

                            case VIP:获取vip号码控制器;break;

}

}

}

);

}

}

 

6、普通服务窗口的细节实现:接着第5步的switch内部代码服务开始

switch(type)

{

case COMMON: String windowName = 第+windowId+号+type +窗口;

       SOP(windowName+正在获取任务);

       Integer num = NumberMachine.getInstance().getCommonManager().fetchServiceNumber();

       if (num!=null)      获取到服务号码,开始服务

       {

              long beginTime = System.currentTimeMillis();

              a、模拟服务过程,线程终止一段时间,时间在最大值和最小值之间

                     为常量,再用一个类来封装用到的常量

                     获取最大随机值int maxRand=

              Constants.MAX_SERVICE_TIME-Constants.MIN_SERVICE_TIME;

              服务时间就为最小服务时间加一个随机时间值

              long serviceTime = new Random().nextInt(maxRand)+1

+ Constants.MIN_SERVICE_TIME;;

              Thread.sleep(serviceTime);

long endTime = System.currentTimeMillis();

              long costTime = endTime – beginTime;

              SOP(windowName+为第+num+个+type+客户完成服务,

耗时+costTime/1000+秒);

}

else c、没有获取到服务号码,没有客户

{

SOP(windowName+没有获取到任务);

  没有任务,休息一下再继续获取,不休息的话循环不停的获取任务

  Thread.sleep(1000);

}

break;

case EXPRESS:

       break;

case VIP:

       break;

}

b、定义专门存放常量的constant

class Constants

{       服务时间定为110秒之间

  public static int MAX_SERVICE_TIME = 10000;

  public static int MIN_SERVICE_TIME = 1000;

}

 

7、快速窗口和VIP窗口代码实现:

       写代码前发现三个窗口类内部实现细节基本相同,所以考虑将普通窗口的细节代码抽取出来。从case COMMON:开始到这个break上边,选重构->提取方法,可以抽取一个commonService方法出来。

private void commonService()

{

String windowName = 第+windowId+号+type+窗口;

SOP(windowName+正在获取任务);

Integer num = NumberMachine.getInstance().getCommonManager().fetchServiceNumber();

if (num!=null)

{

       long beginTime = System.currentTimeMillis();

 

       int maxRand = Constants.MAX – Constans.MIN;

       long serviceTime = new Random().nextInt(maxRand)+1+Constans.MIN;

       Thread.sleep(serviceTime);

 

       long costTime = System.currentTimeMillis()-beginTime;

       SOP(windowName为第num位客户完成服务,耗时costTime/1000秒);

}

else

{

       SOP(windowId+没有取到任务,休息1秒);

       Thread.sleep(1000);

}

}

 

快速窗口和普通窗口基本一样,只是把号码管理器改一下,服务时间不用随机值,为最小值Constants.MIN_SERVICE_TIME;

VIP窗口只需修改号码管理器,服务时间与普通窗口一样用随机值。

 

这两个窗口没有获取到任务时的处理方式与普通窗口不同,需要将其设置为普通窗口来获取普通任务,当有快速或VIP客户时再将其类型修改回来处理相应的业务请求。可直接在处理方法中调用普通业务方法commonService即可

 

代码优化:可将快速窗口和VIP窗口设计为普通窗口的子类,将获取号码管理器的语句修改为一个方法,将没有获取到任务的处理方式修改为一个方法。子类在调用父类方法时将这两个方法覆盖,修改为自己对应的方法即可。

 

8、主类编写与测试:

main(String[] args)

{

a、首先产生4个普通窗口,依次编号并开始服务

for (int i =1; i<5; i++)

{

       ServiceWindow commonWindow = new ServiceWindow();

       设置窗口号码

       commonWindow.setWindowId(i);

       默认就是普通窗口,所以不需要设置窗口类型,直接启动服务即可

       commonWindow.start();

}

b、产生VIP窗口,可设置窗口号,默认1号,开启服务

ServiceWindow vipWindow = new ServiceWindow();

vipWindow.setType(CustomerType.VIP);

vipWindow.start();

c、产生快速窗口,设置与VIP相同

ServiceWindow expressWindow = new ServiceWindow();

expressWindow.setType(CustomerType.EXPRESS);

expressWindow.start();

 

d、模拟客户陆续产生的过程,使用调度线程池,定时产生客户

模拟普通客户

Executors.newScheduledThreadPool(1).scheduleAtFixedRate(

new Runnable()

{

public void run()

{

  产生客户的过程就是向号码机器要号的过程

  从机器拿到对应的号码管理器后再产生号码

  Integer num = NumberMachine.getInstance().getCommonManager()

.generateNewNumber();

              SOP(num+号普通客户等待服务);

}

},

0马上就开始,不用延迟

1,1秒产生一次,控制三种类型客户的比例,可在常量类中定义

COMMON_CUSTOMER_INTERVAL_TIME = 1

TimeUnit.SECONDS

)

);

模拟VIP客户 注意比例VIP:普通:快速=163

可以使用定时器的延迟时间来控制比例 

这个间隔时间可以在上边的常量类中进行定义后在这里使用

Executors.newScheduledThreadPool(1).scheduleAtFixedRate(

new Runnable()

{

public void run()

{

 

}

},

0马上就开始,不用延迟

6,6秒产生一次,控制三种类型客户的比例

  COMMON_CUSTOMER_INTERVAL_TIME *6

TimeUnit.SECONDS

)

);

模拟快速客户

Executors.newScheduledThreadPool(1).scheduleAtFixedRate(

new Runnable()

{

public void run()

{

 

}

},

0,

Constants.COMMON_CUSTOMER_INTERVAL_TIME *2,

TimeUnit.SECONDS

)

);

 

测试中出现的问题,数组角标越界,出现在号码管理器中,当窗口从号码管理器取号时,如果没有客户,客户队列为空,这时remove就会出问题了。在remove前进行判断,判断集合size大于0才remove后返回,不然直接返回null。

获取到任务后,开始服务之前打印提示信息:开始为XXX提供服务。

 

       发现打印信息有些不爽

1号正在获取任务

2号正在获取任务

3号正在获取任务

4号正在获取任务

1号没有获取到任务,休息

2号没有获取到任务,休息

3号没有获取到任务,休息

4号没有获取到任务,休息

1号客户等待服务

2号正在获取任务

3号正在获取任务

3号没有获取到任务,休息

4号正在获取任务

4号没有获取到任务,休息

2号为1号客户提供服务

       因为获取号码的方法加了同步锁,属于阻塞方法,将提示信息放在获取号码的语句下边就可以解决这个问题。

 

       发现VIP窗口没有为普通客户服务的信息。问题出在commonService中

VIP窗口空闲时调用commonService方法,方法内部的窗口类型和客户类型都为type,所以调用时type就为对应的窗口类型

 

private void commonService()

{

String windowName = 第+windowId+号+type+窗口;窗口类型动态修改

Integer num = NumberMachine.getInstance().getCommonManager().fetchServiceNumber();

SOP(windowName+正在获取任务);

if (num!=null)

{                          客户类型为普通客户

       SOP(windowName+开始为第num位+普通type+客户提供服务)

       long beginTime = System.currentTimeMillis();

 

       int maxRand = Constants.MAX – Constans.MIN;

       long serviceTime = new Random().nextInt(maxRand)+1+Constans.MIN;

       Thread.sleep(serviceTime);

 

       long costTime = System.currentTimeMillis()-beginTime;

       SOP(windowName为第num位普通type客户完成服务,耗时costTime/1000秒);

 

 

 

 

原创粉丝点击