笔试题之理发师问题DDD建模

背景

题目

假设有一个理发店只有一个理发师,一张理发时坐的椅子,若干张普通椅子顾客供等候时坐。没有顾客时,理发师睡觉。顾客一到,叫醒理发师。如果理发师没有睡觉,而在为别人理发,他就会坐下来等候。如果所有的椅子都坐满了人,最后来的顾客就会离开。

这是一道笔试题,主要考察面试者的业务建模能力,在短时间内抽象主要业务模型,提炼出模型的属性和方法。然后利用消费者-生存者的模型思维串联业务流程,还有协调者模型的设计,使其能完成桥梁枢纽的功能,这些都是考察的对象。

方案

这里整个领域可以拆分两个上下文,即理发上下文、顾客上下文。

理发上下文在防腐层中,通过开放主机+发布语言的范式获取顾客的详细信息,完成业务的协作。

流程的步骤:

     生产者

  1. 顾客进入理发店,唤醒理发师;
  2. 理发椅是否使用,未使用直接入座;
  3. 理发椅在使用,则进入队列排队;

     消费者

  1. 理发师给理发椅上的顾客理发;
  2. 队列不为空,中拉取顾客,让顾客做到理发椅上。

这是大致的实现思路,下面是我根据描述画了一张业务流程图:

代码 

理发师

它有状态属性,和理发的行为,每次理发需要消耗两秒。

package org.example;

import lombok.Getter;
import lombok.SneakyThrows;

@Getter
public class Barber {
    private BarberStatus status;

    @SneakyThrows
    public void hairCut(CustomerId customerId) {
        System.out.println(String.format("理发师为 %s 理发", customerId.getNum()));
        Thread.sleep(2 * 1000);
    }

    public Barber() {
        this.status = BarberStatus.SLEEP;
    }

    public void weekUp() {
        this.status = BarberStatus.WORKING;
    }
}

 理发师包含睡觉、工作两种状态:

public enum BarberStatus {
    SLEEP, WORKING
}

理发椅

它作为协调者资源共享对象,所以这里有加锁。理发椅在顾客坐下时绑定了顾客,提供完成理发后的资源释放行为。

package org.example;

import lombok.Getter;

@Getter
public class BarberChair {
    private CustomerId customerId;
    private BarberChairStatus status;

    public BarberChair() {
        this.status = BarberChairStatus.FREE;
    }

    public synchronized void seated(CustomerId customerId) {
        this.status = BarberChairStatus.SEATED;
        this.customerId = customerId;
        System.out.printf("用户 %s 坐下%n", customerId.getNum());
    }

    public synchronized void free() {
        this.status = BarberChairStatus.FREE;
        System.out.printf("顾客 %s 理发结束%n", customerId.getNum());
    }

}

理发椅包含被坐还是空闲两种状态: 

public enum BarberChairStatus {
    SEATED, FREE;
}

理发店

它作为一个聚合对象,是整个领域模型的入口,内聚了所有内部对象,所有业务操作都在这里向外暴露。

理发店的属性:理发师、等待的顾客、供顾客坐的椅子数、理发椅。

理发店拥有两个行为:接单、派单

这里可以将理发店理解位理发店系统,系统完成顾客接单,放入队列中。然后异步地派单给理发师,从队列中获取并通知顾客去理发椅理发。

package org.example;

import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;

import java.util.concurrent.ArrayBlockingQueue;

@Setter
@Getter
public class BarberStore {

    private Barber barber;

    private ArrayBlockingQueue<CustomerId> waitingCustomerIds;

    private int customerChairSize;

    private BarberChair barberChair;


    @SneakyThrows
    public void receiveOrder(CustomerId customerId) {
        barber.weekUp();
        if (barberChair.getStatus() == BarberChairStatus.FREE) {
            barberChair.seated(customerId);
            return;
        }
        if (waitingCustomerIds.size() == customerChairSize) {
            System.out.printf("用户 %s 离开%n", customerId.getNum());
            return;
        }
        System.out.printf("用户 %s 等待%n", customerId.getNum());
        waitingCustomerIds.add(customerId);

    }

    public void dispatchOrder() {
        if (barberChair.getStatus() == BarberChairStatus.SEATED) {
            hairCutting(barberChair.getCustomerId());
            return;
        }
        if (!waitingCustomerIds.isEmpty()) {
            CustomerId customerId = waitingCustomerIds.poll();
            barberChair.seated(customerId);
            hairCutting(customerId);
        }
    }

    public BarberStore(int customerChairSize) {
        this.barber = new Barber();
        this.customerChairSize = customerChairSize;
        this.barberChair = new BarberChair();
        this.waitingCustomerIds = new ArrayBlockingQueue<>(customerChairSize);
    }

    @SneakyThrows
    private void hairCutting(CustomerId customerId) {
        barber.hairCut(customerId);
        barberChair.free();
    }
}

顾客

顾客通过顾客Id和理发店聚合进行业务关联。为什么使用CustomerId进行关联呢?

因为顾客并不是理发店聚合内的实体,顾客的生命周期并不和理发店生命周期一致,这家理发店倒闭了,顾客可以去下一家理发店。且顾客也存在较多自己领域内的业务规则。

package org.example;

import lombok.Getter;

@Getter
public class CustomerId {
    private int num;

    public CustomerId(int num) {
        this.num = num;
    }
}

 

测试客户端

模拟两个线程,一个生产者接单,一共接10个单,接单一次休息1s。一个消费者线程派单,一直不停地派单,理发椅有人则去理发,理发完成后让顾客坐到理发椅上,一直重复直到队列为空。

package org.example;

import lombok.SneakyThrows;

/**
 * Hello world!
 */
public class App {
    public static void main(String[] args) {
        BarberStore barberStore = new BarberStore(3);

        // 理发师椅子是顾客和理发师连接的纽带
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                barberStore.receiveOrder(new CustomerId(i));
                mockPauseSecond(1);
            }
        }).start();


        new Thread(() -> {
            while (true) {
                barberStore.dispatchOrder();
            }
        }).start();
    }

    @SneakyThrows
    private static void mockPauseSecond(int i) {
        Thread.sleep(i * 1000);
    }
}

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知始行末

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值