背景
银行门店里有N个窗口,每个窗口都有一名业务员。当有客户到来时,若有空闲窗口,则直接为客户提供服务;若无空闲窗口,则客户进入大厅席位等待叫号。
这是一道笔试题,主要考察面试者的业务建模能力,在短时间内抽象主要业务模型,提炼出模型的属性和方法。然后利用消费者-生存者的模型思维串联业务流程,还有协调者模型的设计,使其能完成桥梁枢纽的功能,这些都是考察的对象。
方案
这里整个领域可以拆分两个上下文,即窗口上下文、客户上下文。
窗口上下文在防腐层(ACL)中,通过开放主机+发布语言的方式获取顾客的详细信息,完成业务的协作。
业务流程:
生产者:
- 客户进入银行,查看是否有空闲窗口;
- 没有窗口入队列,有窗口业务员直接服务;
- 业务完成后,释放空闲窗口。
消费者:
- 有空闲窗口,则出队列,没有则等待;
- 空闲窗口的业务员进行业务作业。
代码
银行
银行作为聚合根实体,是进行业务操作的入口。
拥有空闲窗口(默认两个窗口)、排队用户id的属性;
拥有客户进入、拉取等待客户的行为。
与聚合外的客户实体,通过客户id发生关联。
package org.example;
import java.util.concurrent.ArrayBlockingQueue;
public class Bank {
//默认两个窗口和业务员
private final ArrayBlockingQueue<Window> freeWindows;
private final ArrayBlockingQueue<CustomerId> customerIds;
public Bank() {
ArrayBlockingQueue<Window> freeWindows = new ArrayBlockingQueue<>(2);
Window window1 = new Window(1, new Clerk(1));
Window window2 = new Window(2, new Clerk(2));
freeWindows.add(window1);
freeWindows.add(window2);
this.freeWindows = freeWindows;
this.customerIds = new ArrayBlockingQueue<>(65535);
}
/**
* 有窗口 - 处理业务。
* 没有窗口 - 生产者将客户投递到队列中。
*/
public void customerComeIn(CustomerId customerId) {
//1.如果有空的窗口,直接处理业务。
if (!freeWindows.isEmpty()) {
Window window = freeWindows.poll();
window.handleBiz(customerId);
System.out.printf("业务员:%s 处理,顾客:%s 的业务结束 %n", window.getClerk().getNum(), customerId.getNum());
freeWindows.add(window);
return;
}
//2.没有空的窗口,排队等待。
customerIds.add(customerId);
}
/**
* 系统程序不停轮询,拉取顾客去完成业务
*/
public void pollQueueCustomers() {
if (!freeWindows.isEmpty() && !customerIds.isEmpty()) {
Window window = freeWindows.poll();
CustomerId customerId = customerIds.poll();
window.handleBiz(customerId);
}
}
}
业务员
业务员是最终操作客户业务的实体对象。
package org.example;
import lombok.Getter;
import lombok.SneakyThrows;
@Getter
public class Clerk {
private int num;
public Clerk(int num) {
this.num = num;
}
@SneakyThrows
public void doHandle(CustomerId customerId) {
System.out.printf("业务员:%s 处理,顾客:%s 的业务%n", num, customerId.getNum());
Thread.sleep(2 * 1000);
}
}
客户id
关联客户的id:
package org.example;
import lombok.Getter;
@Getter
public class CustomerId {
private int num;
public CustomerId(int i) {
this.num = i;
}
}
窗口
窗口和业务员一一绑定,一个窗口必定对应一个业务员。
窗口算是一个小的聚合对象,窗口通过业务员完成业务的业务操作。
package org.example;
import lombok.Getter;
@Getter
public class Window {
private int num;
private Clerk clerk;
public Window(int num, Clerk clerk) {
this.num = num;
this.clerk = clerk;
}
public void handleBiz(CustomerId customerId) {
clerk.doHandle(customerId);
}
}
客户端
模拟两个生产者:让客户同时进入银行。
然后模拟一个消费者:不停地获取轮询等待的客户和空闲的窗口,匹配之后让穿客户去窗口处理业务。
package org.example;
import lombok.SneakyThrows;
public class App {
public static void main(String[] args) {
Bank bank = new Bank();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
bank.customerComeIn(new CustomerId(i));
mockPauseSeconds(1);
}
}).start();
new Thread(() -> {
for (int i = 5; i < 15; i++) {
bank.customerComeIn(new CustomerId(i));
mockPauseSeconds(1);
}
}).start();
new Thread(() -> {
while (true) {
bank.pollQueueCustomers();
}
}).start();
}
@SneakyThrows
private static void mockPauseSeconds(int i) {
Thread.sleep(i * 1000);
}
}