1.需求分析
2.面向对象的设计
1.
有三种对应类型的客户:
VIP
客户,普通客户,快速客户
,异步随机生成各种类型的
客户,各类型客户在其对应窗口按顺序依次办理业务
。
2.
各类型客户在其对应窗口按顺序依次办理业务,准确地说,应该是窗口依次叫号。
根据以上的分析,我们可以通过面向对象的角度进行分析:
我分可以设计三个类:
1. 客户:包括普通客户,快速客户,还有VIP客户
2.服务窗口:根据需求我们一共有三种窗口即普通窗口、快速窗口、VIP窗口
功能:
1.提供设置窗口号、窗口类型的方法
2.获取号码并处理客户业务
需要用到多线程:因为是多个窗口同时工作的
1.提供设置窗口号、窗口类型的方法
2.获取号码并处理客户业务
需要用到多线程:因为是多个窗口同时工作的
3. 号码机器:这个号码机器分为三种类型,即产生普通号码,快速号码,VIP号码
功能:产生号码提供给客户、将产生的最前面的号码提供给窗口
需要用到同步:因为两个方法是不同线程操作,当多个线程操作同一个数据的时候会出现问题,所以要让他们互斥。
需要用到单例:因为号码机器只有一台
需要用到同步:因为两个方法是不同线程操作,当多个线程操作同一个数据的时候会出现问题,所以要让他们互斥。
需要用到单例:因为号码机器只有一台
根据分析我们可以设计类,类图为:
NumberManager类
①定义一个用于存储上一个客户号码的成员变量和用于存储所有等待服务的客户号码的队列集合。
②定义一个产生新号码的方法和获取马上要为之服务的号码的方法,这两个方法被不同的线程操作了相同的数据,所以,要进行同步 NumberMachine类
③定义三个成员变量分别指向三个NumberManager对象,分别表示普通、快速和VIP客户的号码管理器,定义三个对应的方法来返回这三个 NumberManager对象。
④将NumberMachine类设计成单例。
package cn.itheima.bank;
/**
* 号码管理器的功能:
* 用户取号:产生号码
* 窗口取号:取得已产生号码的最前面的号码
* @author zhangyun
*/
import java.util.ArrayList;
import java.util.List;
public class NumberManager {
private Integer lastNumber = 1;
private List<Integer> queueNumber = new ArrayList<Integer>();
/*
* 产生一个号码,代表一个用户取号 因为产生号码和获取号码是不同线程对同一个数据进行操作,这样会产生问题,必须让他们互斥,所以加入同步
*/
public synchronized Integer generateNumber() {
queueNumber.add(lastNumber);
return lastNumber++;
}
/* 窗口取得一个号码,代表依次为客户服务 */
public synchronized Integer fetchNumber() {
Integer number = null;
if (queueNumber.size() > 0)
number = queueNumber.remove(0);
return number;
}
}
Constants类
①
定义三个常量:MAX_SERVICE_TIME、MIN_SERVICE_TIME、COMMON_CUSTOMER_INTERVAL_TIME
package cn.itheima.bank;
/**
* 保存了项目中使用的一些常量
* @author zhangyun
*
*/
public class Constants {
public static final int SERVICE_TIME_MIN = 1000;//服务的最小时间
public static final int SERVICE_TIME_MAX = 10000;//服务的最大时间
public static final int COMMON_CUSTOMER_INTERVAL_TIME = 1;//客户到来的的时间
}
ServiceWindow类
①
定义一个start方法,内部启动一个线程,根据服务窗口的类别分别循环调用三个不同的方法。
②定义三个方法分别对三种客户进行服务,为了观察运行效果,应详细打印出其中的细节信息。
/**
* 服务窗口共有三类:普通,快速,VIP
* 1.若快速、VIP窗口没有未用户办理业务的时候,需要为普通客户办理业务
* 2.VIP客户:普通客户:快速客户 = 1:6:3
* 思路:
* 1.窗口应该有对应的窗口ID、窗口名称、窗口类型
* 2.根据客户类型选择对应窗口的业务执行方法
* 3.窗口需要有工作的方法:即取号,并以Sleep代替办理业务的时间
* 4.当快速和VIP窗口没有对应的快速和VIP客户时,需调用普通窗口的方法为普通客户服务
* @author zhangyun
*/
package cn.itheima.bank;
import java.util.Random;
import java.util.concurrent.Executors;
public class ServiceWindows {
/*初始化窗口号及客户类型并提供外部设置的方法*/
private int windowId = 1;
private CustomerType type = CustomerType.COMMON;
public void setWindowId(int windowId) {
this.windowId = windowId;
}
public void setType(CustomerType type) {
this.type = type;
}
/*定义窗口启动方法,根据不同客户类型启动不同窗口
*这里要注意:若start中没用定义新线程来执行方法,那么在main方法中创建多个窗口的时候,第一个窗口被启动时就会一直循环。
*这里是完成整个项目犯错的地方,特别要注意
*/
public void start(){
Executors.newSingleThreadExecutor().execute(new Runnable(){
public void run(){
switch(type){
case COMMON :
/*因为窗口启动之后需要一直处理该类型的客户,所以要一直使用该窗口的方法*/
while(true)
commonWindow();
case EXPRESS :
while(true)
expressWindow();
case VIP :
while(true)
vipWindow();
}
}
});
}
/*普通窗口*/
private void commonWindow() {
String windowName = "第" + windowId + "号" + type + "窗口";
Integer number = NumberMechine.getNumberMechine().getCommonNumber().fetchNumber();
System.out.println(windowName + "正在获取任务");
if (number != null)
try {
int maxRondom = Constants.SERVICE_TIME_MAX - Constants.SERVICE_TIME_MIN;
int serviceTime = new Random().nextInt(maxRondom) + 1 + Constants.SERVICE_TIME_MIN;
Thread.sleep(serviceTime);
System.out.println(windowName + "为第" + number + "号" + "普通客户服务,耗时:" + serviceTime/1000 + "秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
else
System.out.println(windowName + "没有获取到任务,等待一秒钟");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*快速窗口*/
private void expressWindow() {
String windowName = "第" + windowId + "号" + type + "窗口";
Integer number = NumberMechine.getNumberMechine().getExpressNumber().fetchNumber();
if (number != null)
try {
int serviceTime = Constants.SERVICE_TIME_MIN;
Thread.sleep(serviceTime);
System.out.println(windowName + "为第" + number + "号" + "快速客户服务,耗时:" + serviceTime/1000 + "秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
else
System.out.println(windowName + "未获取到任务,进入普通任务获取阶段");
commonWindow();
}
/*VIP窗口*/
private void vipWindow() {
String windowName = "第" + windowId + "号" + type + "窗口";
Integer number = NumberMechine.getNumberMechine().getVipNumber().fetchNumber();
if (number != null)
try {
int maxRondom = Constants.SERVICE_TIME_MAX - Constants.SERVICE_TIME_MIN;
int serviceTime = new Random().nextInt(maxRondom) + 1 + Constants.SERVICE_TIME_MIN;
Thread.sleep(serviceTime);
System.out.println(windowName + "为第" + number + "号" + "VIP客户服务,耗时:" + serviceTime/1000 + "秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
else
System.out.println(windowName + "未获取到任务,进入普通任务获取阶段");
/*若VIP窗口未取得VIP号码管理中的号码,则为普通客户服务*/
commonWindow();
}
}
MainClass
类
①用for循环创建出4个普通窗口,再创建出1个快速窗口和一个VIP窗口。
②接着再创建三个定时器,分别定时去创建新的普通客户号码、新的快速客户号码、新的VIP客户号码。
package cn.itheima.bank;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* 测试的客户端类
* @author zhangyun
*
*/
public class mainClass {
public static void main(String[] args) {
//生成4个普通窗口并开启
for(int i = 1; i<=4; i++){
ServiceWindows commonWindow = new ServiceWindows();
commonWindow.setType(CustomerType.COMMON);
commonWindow.setWindowId(i);
commonWindow.start();
}
//生成1个快速窗口并开启
ServiceWindows expressWindow = new ServiceWindows();
expressWindow.setType(CustomerType.EXPRESS);
expressWindow.start();
//生成1个VIP窗口并开启
ServiceWindows vipWindow = new ServiceWindows();
vipWindow.setType(CustomerType.VIP);
vipWindow.start();
//按比例增加普通客户
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new Runnable(){
public void run(){
Integer serviceNumber = NumberMechine.getNumberMechine().getCommonNumber().generateNumber();
System.out.println("第" + serviceNumber + "号普通客户正在等待服务");
}
},
0,
Constants.COMMON_CUSTOMER_INTERVAL_TIME,
TimeUnit.SECONDS);
//按比例增加快速客户
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new Runnable(){
public void run(){
Integer serviceNumber = NumberMechine.getNumberMechine().getExpressNumber().generateNumber();
System.out.println("第" + serviceNumber + "号快速客户正在等待服务");
}
},
0,
Constants.COMMON_CUSTOMER_INTERVAL_TIME * 2,
TimeUnit.SECONDS);
//按频比例增加VIP客户
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new Runnable(){
public void run(){
Integer serviceNumber = NumberMechine.getNumberMechine().getVipNumber().generateNumber();
System.out.println("第" + serviceNumber + "号VIP客户正在等待服务");
}
},
0,
Constants.COMMON_CUSTOMER_INTERVAL_TIME * 6,
TimeUnit.SECONDS);
}
}
CustomerType枚举类
①系统中有三种类型的客户,所以用定义一个枚举类,其中定义三个成员分别表示三种类型的客户。
②重写toString方法,返回类型的中文名称。这是在后面编码时重构出来的,刚开始不用考虑。
package cn.itheima.bank;
/**
* 因为客户类型是固定的三种类型,所以使用枚举
* 判断当前枚举类型,并覆盖toString方法,根据枚举类型指定对应的客户类型name
*@author zhangyun
*/
public enum CustomerType {
COMMON,EXPRESS,VIP;
public String toString(){
String name = null;
switch(this){
case COMMON :
name = "普通客户";
return name;
case EXPRESS :
name = "快速客户";
return name;
case VIP :
name = "VIP客户";
return name;
}
return null;
}
}