您的位置:首页 > 职场人生

经典面试项目—银行业务调度系统

2013-09-24 12:40 465 查看
上一篇文章中介绍了交通灯管理系统,对我们学习面向对象编程的理解意义非凡,继这个面试者完成这个项目被软通动力聘用之后,又有一位面试者应聘软通动力,同样的还是交给这位面试者一个项目拿回家去做,提交审核通过之后即可完成面试。这个项目是模拟银行叫号业务的系统,我们平时去银行的时候可以发现,不管办理什么样的柜台业务,大厅经理都会从取票机上面取出一张号码票给我们,然后只要等待广播叫道各自拿到的号码到相应的柜台上办理业务即可,这个程序就是模拟这样一个过程,当我们使用面向对象编程时仔细分析潜在的对象,这种思考方式符合人类的大脑思维习惯,有助于将业务转化为具体的技术需求,好了,下面还是使用Java语言来对这个项目进行面向对象分析及设计。

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

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

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

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

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

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

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

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

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

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

思路分析,将业务需求转化为具体的技术需求

1, 通过分析对象可以发现,有客户、业务窗口两大对象,客户对象有类型属性(VIP、快速、普通三种类型),业务窗口也有对应的三种类型,可以考虑将每个窗口封装为一个对象,但是1-4号窗口是面向一种类型客户开放的,而5、6号窗口在自己窗口没有客户并且1-4号窗口有等待办理业务的客户时可以对普通客户提供服务,办理业务期间如果又来了一位快速客户或者VIP客户则对其优先安排办理业务,考虑到这种情况,再结合现实银行中业务处理方式,我们可以单独模拟一个类似“取票器”的对象,将储存客户的功能单独分离出来封装进这个对象中。

2, 各类型客户在其对应窗口按前后顺序依次办理业务,很简单这说明我们应该模拟一个标准的队列,将新来的客户对象按前后依次添加进队列中,客户办理完业务之后再将队列中第一位客户删除,很显然这里是动态储存,普通数组肯定无法方便实现,可以借助ArrayList实现队列。

3, 异步随机生成客户,并且三种类型客户产生慨率有了说明,那么我们可以利用多线程并发执行实现这点。

4, 每个客户办理业务所花费的时间有一个最大值和最小值的限制,通过限定范围的随机数可以模拟。

OK,基本思路以及需求以及列出,待完善的细节在考虑具体哪些类承担哪些属性和方法时就会逐渐浮出水面,让我们一起来完成这个项目吧

 

NumberManager类,

1,定义一个产生号码的机器类,实现向队列添加以及删除客户两个方法。

2,定义一个用于存储上一个客户号码的成员变量和用于存储所有等待服务的客户号码的队列集合。

3,定义一个产生新号码的方法和获取马上要为之服务的号码的方法,这两个方法被不同的线程操作了相同的数据,所以,要进行同步。

import java.util.ArrayList;
import java.util.List;

public class NumberManager {
// 一个号码表示一个客户
private int lastNumber = 0;
// 号码的集合,模拟现实中排队
private List<Integer> queueNumbers = new ArrayList<Integer>();

// 向队列中添加号码,模拟一个客户拿到号码票之后在大厅等待
public synchronized Integer generateNewNumber(){
queueNumbers.add(++lastNumber);
return lastNumber;
}
// 从队列中删除第一个号码,模拟客户办完业务之后离开
public synchronized Integer fetchNumber(){
if(queueNumbers.size()>0){
return (Integer)queueNumbers.remove(0);
}else{
return null;
}
}
}


 

NumberMachine类,

1,定义三个成员变量分别指向三个NumberManager对象,分别表示普通、快速和VIP客户的号码管理器,定义三个对应的方法来返回这三个NumberManager对象。

2,由于只需要一个产生号码票的机器,所以这里应该将NumberMachine类设计为单例。

 

 
public class NumberMachine {
//将NumberMachine类设置为单例
private NumberMachine(){}
private static NumberMachine instance = new NumberMachine();
public static NumberMachine getInstance(){
return instance;
}

// 通过不同的方法获取不同类型的客户队列
// 这是为了实现不同类型窗口有针对性的为特定类型的客户服务
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;
}

}

 

ServiceWindow类,

1, 定义一个start方法,内部启动一个线程,根据服务窗口的类别分别循环调用三个不同的方法。

2,从上面的需求就可以看到我们应该将客户的三种类型封装起来,类型数量固定,所以枚举比较合适。

3,定义三个方法分别对三种客户进行服务,为了观察运行效果,应详细打印出其中的细节信息。

 

import java.util.Random;
import java.util.concurrent.Executors;

/**
* 没有把VIP窗口和快速窗口做成子类,是因为实际业务中的普通窗口可以随时被设置为VIP窗口和快速窗口。
* */
public class ServiceWindow {
// 窗口类型和号码
private CustomerType type = CustomerType.COMMON;
private int number = 1;

public ServiceWindow(CustomerType type, int number) {
this.type = type;
this.number = number;
}

public CustomerType getType() {
return type;
}

// 提供一个设置 窗口类型的方法,可以实现在不同情况下,改变窗口的类型面对客户服务
public void setType(CustomerType type) {
this.type = type;
}

public void setNumber(int number){
this.number = number;
}

//开放窗口服务,根据窗口类型选择对应的服务方式
public void start(){
Executors.newSingleThreadExecutor().execute(
new Runnable(){
public void run(){
do {
switch(type){
case COMMON:
commonService();
break;
case EXPRESS:
expressService();
break;
case VIP:
vipService();
break;
}
} while(true);
}
}
);
}

//普通窗口的服务方式,转为普通客户服务
private void commonService(){
String windowName = "第" + number + "号" + type + "窗口";
System.out.println(windowName + "开始获取普通任务!");
Integer serviceNumber = NumberMachine.getInstance().getCommonManager().fetchNumber();
if(serviceNumber != null ){
System.out.println(windowName + "开始为第" + serviceNumber + "号普通客户服务");
int maxRandom = Constants.MAX_SERVICE_TIME - Constants.MIN_SERVICE_TIME;
int serviceTime = new Random().nextInt(maxRandom)+1 + Constants.MIN_SERVICE_TIME;

try {
Thread.sleep(serviceTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(windowName + "完成为第" + serviceNumber + "号普通客户服务,总共耗时" + serviceTime/1000 + "秒");
}else{
System.out.println(windowName + "没有取到普通任务,正在空闲一秒");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

// 快速窗口的服务方式,当没有快速客户时向普通客户开放
private void expressService(){
Integer serviceNumber = NumberMachine.getInstance().getExpressManager().fetchNumber();
String windowName = "第" + number + "号" + type + "窗口";
System.out.println(windowName + "开始获取快速任务!");
if(serviceNumber !=null){
System.out.println(windowName + "开始为第" + serviceNumber + "号快速客户服务");
int serviceTime = Constants.MIN_SERVICE_TIME;
try {
Thread.sleep(serviceTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(windowName + "完成为第" + serviceNumber + "号快速客户服务,总共耗时" + serviceTime/1000 + "秒");
}else{
System.out.println(windowName + "没有取到快速任务!");
commonService();
}
}

// VIP窗口的服务方式,当没有VIP客户时向普通客户开放
private void vipService(){
Integer serviceNumber = NumberMachine.getInstance().getVipManager().fetchNumber();
String windowName = "第" + number + "号" + type + "窗口";
System.out.println(windowName + "开始获取VIP任务!");
if(serviceNumber !=null){
System.out.println(windowName + "开始为第" + serviceNumber + "号VIP客户服务");
int maxRandom = Constants.MAX_SERVICE_TIME - Constants.MIN_SERVICE_TIME;
int serviceTime = new Random().nextInt(maxRandom)+1 + Constants.MIN_SERVICE_TIME;
try {
Thread.sleep(serviceTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(windowName + "完成为第" + serviceNumber + "号VIP客户服务,总共耗时" + serviceTime/1000 + "秒");
}else{
System.out.println(windowName + "没有取到VIP任务!");
commonService();
}
}
}


CustomerType类,

1,系统中有三种类型的客户,所以用定义一个枚举类,其中定义三个成员分别表示三种类型的客户。

2, 重写toString方法,返回类型的中文名称。这是在后期对程序测试调优时蹦出来的需求。

 

public enum CustomerType {
COMMON,EXPRESS,VIP;
//测试程序打印客户类型时需要覆盖toString方法得到客户类型的字符串表示方式
public String toString(){
String name = null;
switch(this){
case COMMON:
name = "普通";
break;
case EXPRESS:
name = "快速";
break;
case VIP:
name = name();
break;
}
return name;
}
}


Constants接口,

1, 设置每个客户办理业务时间的最大值和最小值

2, 生成一个普通客户所需要的时间。

 

public interface Constants {
public static int MAX_SERVICE_TIME = 10000; //10秒!
public static int MIN_SERVICE_TIME = 1000; //1秒!

/*每个普通窗口服务一个客户的平均时间为5秒,一共有4个这样的窗口,也就是说银行的所有普通窗口合起来
* 平均1.25秒内可以服务完一个普通客户,再加上快速窗口和VIP窗口也可以服务普通客户,所以,
* 1秒钟产生一个普通客户比较合理,*/
public static int COMMON_CUSTOMER_INTERVAL_TIME = 1;
}


MainClass类,

1, 源源不断产生不同类型的客户,使用多线程并发执行,根据客户类型不同,产生一个客户的时间也不尽相同,既然有时间间隔,就用定时器来实现。

2, 创建窗口并开放服务,运行程序查看效果。

import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class MainClass {

public static void main(String[] args) {
// 开启六个服务窗口并调用start方法对外服务
int i=1;
for(; i < 5;){
ServiceWindow window =  new ServiceWindow(CustomerType.COMMON, i++);
window.start();
}

ServiceWindow expressWindow =  new ServiceWindow(CustomerType.EXPRESS, i++);
expressWindow.start();

ServiceWindow vipWindow =  new ServiceWindow(CustomerType.VIP, i);
vipWindow.start();

// 为源源不断而来的客户根据客户类型分配号码并添加到队列中
// 设置不同类型客户生成速度,
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new Runnable(){
public void run(){
Integer serviceNumber = NumberMachine.getInstance().getCommonManager().generateNewNumber();
System.out.println("第" + serviceNumber + "号普通客户正在等待服务!");
}
},
0,
Constants.COMMON_CUSTOMER_INTERVAL_TIME,
TimeUnit.SECONDS);

Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new Runnable(){
public void run(){
Integer serviceNumber = NumberMachine.getInstance().getExpressManager().generateNewNumber();
System.out.println("第" + serviceNumber + "号快速客户正在等待服务!");
}
},
0,
Constants.COMMON_CUSTOMER_INTERVAL_TIME * 2,
TimeUnit.SECONDS);

Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new Runnable(){
public void run(){
Integer serviceNumber = NumberMachine.getInstance().getVipManager().generateNewNumber();
System.out.println("第" + serviceNumber + "号VIP客户正在等待服务!");
}
},
0,
Constants.COMMON_CUSTOMER_INTERVAL_TIME * 6,
TimeUnit.SECONDS);
}

}

 
到目前为止,这样的一个简易模拟银行业务的调度系统就实现完成了,在面向对象设计与编程过程中,分析需要哪些类,一个类中分别有什么样的属性和方法应该在没有编码之前就列出一个比较具体的清单,根据这样一个清单,有经验的程序员大致就能判断这样一个系统能不能实现了,也就是说一个系统各个组件能不能一起协同完成任务在一开始架构设计时已经决定了。具体的一些处理细节问题,在编码或者测试的时候自然就会浮出水面,发现时再作相应的处理即可,学习面向对象设计思想有助于提升对我们的思维广度,谁拥有数据,谁就对外提供操作这个数据的方法。谢谢。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息