黑马程序员---银行业务调度系统学习笔记

------- Java、.NetAndroid培训期待与您交流!------- 

一、银行业务调度系统的项目需求

二、面向对象的分析与设计

        对该系统的需求分析,至少可以得到三个对象:客户、取号机、业务窗口。

  • 客户类。客户分为三种类型:普通客户、快速客户和VIP客户,所以很容易得出客户类用枚举来声明在合适不过。因为不涉及到对客户属性的调用,所以不必声明其他方法,该枚举很简单。
  • 取号机。现在的银行业务应用中,都会用一台产生号码的机器来管理客户的业务顺序,只有被叫到的客户才会到业务窗口办理业务。该机器会产生三种类型的号码,这个是根据客户的类型来判断的。所以定义一个 NumberMachine 类来管理号码段,且一个银行中只有一台取号机,所以这个类被定义成单例模式(作用是实现多个线程的数据共享),这样保证了数据的号码唯一性,不会出现重号的情况。
  • 业务窗口。需求中提供了6个业务窗口,分三种来对应处理三种类型客户的业务。实际应用中,业务窗口数是可调的,即三种类型业务窗口具有不确定性,快速窗口和VIP窗口也可能被分配为普通窗口,所以把对业务窗口的生成用判断语句来实现,这样可以随意更改窗口类型,一般业务窗口个数都是固定值。在窗口办理业务的时候,是通过叫号来完成。而叫号其实是在调用取号机上的正等待服务的号码,所以取号机上有一个产生号码的方法。
  • 除上诉直观关对象外,对于生成新号码还有一个号码管理器来对其进行管理,这些号码都是正在等待服务的客户号码。学习视屏之后,根据面向对象的思想,可以发现生成新号码这个方法是在调用号码管理器中的属性,所以把号码生成的方法定义在该类中。

        该系统类图:

 三、程序分析

  1、NumberMachine 和NumberManager 两个类是办理窗口业务的前提。

  • 前面分析过取号机,需要被设计成单例,才能满足多线程的数据共享。而共享数据分为三类,分别是普通类型、快速类型和VIP类型,这三类号码都是号码管理器的一个实例。所以将这三类号码定义在取号机NumberMachine  中,保证它们的唯一性。该部分代码比较简单,重点在于能够想到通过单例设计来完成。
  • 通过前面分析号码产生机制,可以发现,号码的生成可以用一个计时器来实现,这里多线程的访问方式是一个在产生(generateNewNumber)号码,一个在取出(fetchNumber)号码,所以这两个方法设计成同步函数。不同线程在调用这两个方法时,当调用其中的一个,另一个方法也会被锁住。号码的存储同交通管理系统中同 Road 类上的车的设计一样,存入集合中,都要求满足先进先出的原则(可以悬在LinkedList,这里换ArrayList ,那么我们只需移除0角标的元素即可)。
      
            号码的生成我们通过计时器来完成,交通管理系统中有详细讲解。号码生成时间比为:VIP客户:普通客户:快速客户 =  1:6:3,若1s生成一个普通客户,那么6s生成一个VIP客户,2s生成一个快速客户。我们的计时器也按照这个时间频率来设置。代码如下:  
    public void getCustomerNumber(final CustomerType type){
        Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(
            new Runnable(){
                @Override
                public void run() {
                    Integer serviceNumber = generateNewNumber();
                    System.out.println("第" + serviceNumber + "号" + type.toString() + "客户正在等待服务...");
                }
            }, 
            0, 
            getPeriod(type), 
            TimeUnit.SECONDS);
    }
    //通过类型判断来传入不同的时间频率
    private long getPeriod(CustomerType type){
        long period = 2147483647;
        long commonTime = Constants.COMMON_CUSTOMER_INTERVIEW_TIME;
        switch (type) {
            case COMMON:
                period = commonTime;
                break;
            case EXPRESS:
                period = commonTime * 2;
                break;
            case VIP:
                period = commonTime * 6;
                break;
        }
        return period;
    } 

            为了提供代码复用性,定义了getPeriod 方法来传递时间频率,这样只需定义一个计时器就可以对三种类型号码进行操作了。这里涉及到了常量的应用,需求中也有说明,即办理业务的最大值和最小值,还有普通客户的的产生频率,若为了体现的更真实,可以将普通客户的产生频率定义为一个随机值,其它两类客户产生频率也随这个随机值变化。这里为了方便,将普通客户的产生频率固定在1s一个。关于常量值的定义也可以通过配置文件的方式来传入,这样操作会更简单,不会涉及到源码。

  2、业务窗口类 ServiceWindow

        该系统设计的第二个难点,关于三种类型窗口的定义。需求中一共有6个窗口,也就是6个线程,该6个线程对普通号码都是共享的。

        一个ServiceWindow 对象即一个线程,首先得定义一个开启线程的方法。这里也用到了线程池工具类 Executors,查阅API 可得在 Executor 接口中有一个开启线程的方法 execute(Runnable command),使用其子接口ScheduledExecutorService 来定义该窗口类型。

        public void start(){
                Executors.newSingleThreadScheduledExecutor().execute(
                        new Runnable() { 
                                public void run() {
                                        while (true) {
                                                if(type.name()=="COMMON")
                                                        commonWindow();
                                                else
                                                        serviceWindow();
                                        }
                                }
                        });
        }

        下面是处理具体的办理类型的声明,这个细节难度很大,容易出错。这块代码涉及到了所有定义过的类,容易搞糊涂,所以主要是弄清细节。原理其实很简单,客户到对应业务窗口办理相关业务,办理业务时间有最小到最大这么一个随机值,业务事件通过sleep 方法来完成。然后就是一个在测试类中循环调用的过程,所以原理简单,但是细节麻烦。先把普通窗口的处理声明写好,方法声明如下:

        private void commonWindow(){
                String windowName = windowID + "号" + type + "窗口";
                System.out.println(windowName + "正在获取普通任务...");
                Integer serviceNumber = NumberMachine.getInstance().getCommonManager().fetchNumber();
                if (serviceNumber != null) {
                        System.out.println(windowName + "开始为" + serviceNumber + "号普通客户服务...");
                        int serviceTime = new Random().nextInt(Constants.MAX_SERVICE_TIME) + 1;
                        try {
                                Thread.sleep(serviceTime * 1000);
                        } catch (InterruptedException e) {
                                throws new RuntimeException(e);
                        }
                        System.out.println(windowName + "完成为" + serviceNumber + "号普通客户服务,耗时" + serviceTime + "秒!");
                } else {
                        System.out.println(windowName + "没有取到普通任务,休息1秒。");
                        try {
                                Thread.sleep(1000);
                        } catch (InterruptedException e) {
                                throws new RuntimeException(e);
                      }
                }
        }

        快速和VIP窗口的声明就是普通型的一个子方式,只需在客户类型上做哈修改就满足条件。需求中还要求快速和VIP窗口也能处理普通业务,所以这两个业务最后需要调用普通业务的方法。快速和VIP业务都属于特殊业务,所以可以通过抽取代码通过客户类型来判断办理业务的类型,这样提高代码的复用性。代法如下:

	//随机服务类型窗口,根据服务类型变更。
	private void serviceWindow(){
		String windowName = windowID + "号" + type + "窗口";
		System.out.println(windowName + "正在获取" + type + "任务...");
		Integer serviceNumber = getServiceNumber();
		if (serviceNumber != null) {
			System.out.println(windowName + "开始为" + serviceNumber + "号" + type + "客户服务...");
			int serviceTime = getServiceTime();
			try {
				Thread.sleep(serviceTime * 1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(windowName + "完成为" + serviceNumber + "号" + type + "客户服务,耗时" + serviceTime + "秒!");
		} else {
			commonWindow();
		}
	}
	/*根据服务类型(客户类型)获取取号机生成的对应类型的号码*/
	private Integer getServiceNumber(){
		Integer serviceNumber = null;
		switch (type) {
			case EXPRESS:
				serviceNumber = NumberMachine.getInstance().getExpressManager().fetchNumber();
				break;
			case VIP:
				serviceNumber = NumberMachine.getInstance().getVIPManager().fetchNumber();
				break;
		default:
			break;
		}
		return serviceNumber;
	}
	/*三种类型的客户办理业务的随机时间不同,但快速客户耗时最少(设置为1s)*/
	private int getServiceTime(){
		int serviceTime;
		if (type == CustomerType.VIP) {
			serviceTime = new Random().nextInt(Constants.MAX_SERVICE_TIME) + 1;
		} else {
			serviceTime = Constants.MIN_SERVICE_TIME;
		}
		return serviceTime;
	}

四、总结

        交通灯系统和银行系统的对比:

  • 相似点:都涉及到集合的应用、枚举的定义和计时器技术。
  • 着重点:交通灯处理的重点是关于 Road 类的简化分析,枚举元素的定义方式和计时器的应用。
                    银行系统的着重点针对客户类型而产生的三种类型的业务号码,计时器的应用以及多线程技术的安全问题。
  • 互比性:交通灯系统处理都是单线程,所以不用考虑安全性。但银行系统用到了多线程,这时就需要考虑安全性,多线程的问题也是交通灯中所没有遇到的,我们在对数据进行加锁时,需要找准需要被锁定的数据,即明确哪些数据是大家共享的。
  • 面向对象:对需求的分析,得把握住所有必须的对象,同时抓住隐藏的对象,这点最难,这个不是一蹴而就的,需要在实际的开发中慢慢积累,见的多了,就会了。就像张老师在所说的,如果没有这方面的经验,他也不知道用这样的方式来进行设计。所以先得多理解前辈的设计思想,在将其应用,然后转换成自己的设计思想。面向对象的分析,记住一点:谁拥有数据,谁就是对外提供操作这些数据的方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值