首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >建立呼叫中心的模型

建立呼叫中心的模型
EN

Code Review用户
提问于 2014-02-28 18:50:50
回答 5查看 6.8K关注 0票数 23

这是我的要求(来自“破解编码面试”一书)

假设您有一个拥有三个级别员工的呼叫中心:新鲜员工、技术主管(TL)和产品经理(PM)。可以有多个员工,但只有一个TL或PM。一个来电必须分配给一个新的谁是免费的。如果一个新人不能处理这个电话,他或她必须将电话升级为技术主管。如果TL没有空闲或无法处理,则应将调用升级到PM。针对这个问题设计类和数据结构。实现一个方法getCallHandler()

这就是我的实现:

代码语言:javascript
复制
public interface CallAllocator {
    public Employee getCallHandler() throws NoEmployeeInTheHouseException;
    void setTL(TechnicalLead technicalLead);
    void setPM(ProductManager productManager);
    void addFresher(Fresher fresher);
}

接口的实现:

代码语言:javascript
复制
public class CallAllocatorImpl implements CallAllocator {

    private TechnicalLead technicalLead;
    private ProductManager productManager;
    private List<Fresher> freshers = new ArrayList<Fresher>();

    @Override
    public Employee getCallHandler() throws NoEmployeeInTheHouseException {

        if (freshers.isEmpty() && technicalLead == null && productManager == null) {
            throw new NoEmployeeInTheHouseException();
        }

        if (!freshers.isEmpty()) {
            Employee fresher = freshers.get(new Random().nextInt(freshers.size()));
            if (fresher.getCanHandle()) {
                return fresher;
            }
        }

        if (technicalLead != null && technicalLead.getCanHandle()) {
            return technicalLead;
        }

        if (productManager != null && productManager.getCanHandle()) {
            return productManager;
        }

        throw new NoEmployeeInTheHouseException();

    }
    @Override
    public void setTL(TechnicalLead technicalLead) {
        this.technicalLead = technicalLead;
    }
    @Override
    public void setPM(ProductManager productManager) {
        this.productManager = productManager;
    }
    @Override
    public void addFresher(Fresher fresher) {
        if (fresher.isFree()) {
            freshers.add(fresher);
        }
    }
}

雇员班:

代码语言:javascript
复制
public class Employee {
    private boolean free;
    private boolean canHandle;
    public boolean isFree() {
        return free;
    }
    public void setFree(boolean free) {
        this.free = free;
    }
    public boolean getCanHandle() {
        return canHandle;
    }
    public void setCanHandle(boolean canHandle) {
        this.canHandle = canHandle;
    }
}

我有三个类的名字:新鲜,TechnicalLead和ProductManager。它们都扩展了员工,但不覆盖任何方法或任何东西。

这是我的TestClass:

代码语言:javascript
复制
public class TestClass {

    public static void main(String[] args) throws NoEmployeeInTheHouseException {

        CallAllocator callAllocator = new CallAllocatorImpl();

        Fresher fresherOne = new Fresher();
        fresherOne.setCanHandle(false);
        fresherOne.setFree(true);

        Fresher fresherTwo = new Fresher();
        fresherTwo.setCanHandle(true);
        fresherTwo.setFree(true);

        Fresher fresherThree = new Fresher();
        fresherThree.setCanHandle(false);
        fresherThree.setFree(true);

        Fresher fresherFour = new Fresher();
        fresherFour.setCanHandle(false);
        fresherFour.setFree(false);

        callAllocator.addFresher(fresherOne);
        callAllocator.addFresher(fresherTwo);
        callAllocator.addFresher(fresherThree);
        callAllocator.addFresher(fresherFour);

        TechnicalLead technicalLead = new TechnicalLead();
        technicalLead.setCanHandle(false);
        technicalLead.setFree(true);

        callAllocator.setTL(technicalLead);

        ProductManager productManager = new ProductManager();
        productManager.setCanHandle(true);
        productManager.setFree(true);

        callAllocator.setPM(productManager);

        Employee callHandler = callAllocator.getCallHandler();
        System.out.println(callHandler.getClass().getSimpleName());
    }
}

那么我如何改进这段代码呢?有什么建议吗?

EN

回答 5

Code Review用户

回答已采纳

发布于 2014-02-28 19:30:23

首先,我不确定您是否真正实现了所编写的需求。描述说:

来电必须分配给一个新的谁是自由的。如果一个新人不能处理这个电话,他或她必须升级为技术领导。

这听起来就像如果没有免费的新生,就根本不应该处理调用(抛出的异常?),而不是跳到TL。这种方式作为现实世界的要求是有意义的:如果没有新生,打电话的人可能需要稍晚一点再打电话,而不是浪费技术上的领导时间,让一个新人能够处理。例如,无论调用什么,都可以计划捕获异常并执行addCallToUnhandledCallQueue()或其他任何操作。

这也更多地涉及到这个问题的实质,我相信这是试图让你用责任链的模式来回答。在这种模式中,负责处理命令(在本例中是调用)的每个对象(在本例中是Employee)都包含检查它是否能够处理给定命令的逻辑,如果不能,也知道链中要调用的下一个对象。

这种模式的一个好处是坚持开放/封闭原则。正如您将在下面看到的,做一些事情,比如添加一个新的雇员类型或者稍微改变一下结构,不太可能需要您在getCallHandler()中摆弄if{...} else{...}逻辑。此外,这意味着Employees只需要知道他们的顶头上司,而不是一些大师必须知道并坚持整个员工结构(这很快就会变得不愉快,特别是如果您需要添加其他方法,这些方法也需要了解这种结构)。

一个重要的好处是,考虑到这显然是一个面试问题,如果有人在面试中问我这个问题,我很确定他们会希望我谈论这个模式,所以即使出于任何原因,你最终决定有一个更好的解决方案,理解这个问题也很重要,如果能够明智地描述你拒绝它的原因。

因此,使用此模式,您的getCallHandler(Call call)方法将如下所示:

代码语言:javascript
复制
Employee fresher = getAnyFreeFresher(); //Should throw if there are none
return fresher.handle(call);

然后,Employee类看起来如下所示:

代码语言:javascript
复制
public class Employee{
    private Employee boss;

    private bool canHandle(Call call){
    //...
    }

    public Employee handle(Call call){
        if(canHandle(call)){
            return this;
        }
        if(boss == null){
            //Nobody in the chain could handle, throw
        }
        return boss.handle(call);
    }
}

这是一个非常粗略的大纲,需要详细说明老板是如何设置的,您可能希望员工类型从Employee继承来实现canHandle等等。

票数 20
EN

Code Review用户

发布于 2014-02-28 19:15:30

总的来说,代码很容易读懂。

一些小的挑剔的东西。

不要使用booleans,而要使用enums

代码语言:javascript
复制
public enum EmployeeStatus {
    OnCall,
    Available        
}

这允许您在需求更改时添加更多的状态:

代码语言:javascript
复制
public enum EmployeeStatus {
    OnCall,
    Available,
    OutToLunch,
    OnVacation,
    OnBreak
}

我不喜欢getCallHandler()可以填充新生。我会注射这个列表,或者是一家能做到这一点的工厂。这将使代码解耦,并允许将来进行更容易的单元测试。

我也不喜欢这套方法。使用继承应该可以消除这方面的需求。诚然,我目前还没有找到解决办法,但有一个办法。基本上,通过使用不同的方法来处理不同的实例,您将自己绑定到这三种类型中。如果添加了第四种类型,比如ProductExpert,会发生什么?现在您必须更改这个类来处理它。

canHandle方法似乎应该是基于状态枚举的计算。这样,您只需在类中设置一个标志,而不是两个。

代码语言:javascript
复制
public class Employee {

    // code

    public boolean isFree() {
        return status == EmployeeStatus.Available;
    }

    // code
}

Fresher fresherTwo = new Fresher();
fresherTwo.setStatus(EmployeeStatus.Available);

我也不喜欢‘`NoEmployeeInTheHouseException’这个名字,我觉得它有点太因果了。我会做一些'NoEmployeeAvailableException‘之类的事情。这让人觉得更有公事性。

这是一个很好的开始,我很高兴你关心这件事。继续努力吧。

票数 13
EN

Code Review用户

发布于 2014-03-01 08:58:16

首先我将评论你的执行情况,然后我将在最后提出我的版本。

从上到下,我们开始了:

代码语言:javascript
复制
public interface CallAllocator {
    public Employee getCallHandler() throws NoEmployeeInTheHouseException;
    void setTL(TechnicalLead technicalLead);
    void setPM(ProductManager productManager);
    void addFresher(Fresher fresher);
}
  • "Employee“这个词对处理电话的人来说太笼统了。我会叫它ICallHandler。在讨论处理调用时,实际的employee类是不相关的,它们不应该是模型设计的一部分。
  • setTLsetPMaddFresher方法都使用过于特定于实现的术语和参数。接口定义应该尽可能抽象。

下一步:

代码语言:javascript
复制
if (freshers.isEmpty() && technicalLead == null && productManager == null) {
    throw new NoEmployeeInTheHouseException();
}

我看到在您的实现中,您将新生/领导/管理器的可用性处理为空或空。这是状态管理,而不是建模。最好是使用显式接口方法来捕捉模型是否可用的概念。

代码语言:javascript
复制
if (!freshers.isEmpty()) {
    Employee fresher = freshers.get(new Random().nextInt(freshers.size()));
    if (fresher.getCanHandle()) {
        return fresher;
    }
}
  • getCanHandle真的很尴尬,canHandle会更自然一些
  • 获取下一个可用的更新器的概念应该有它自己的方法:您可以考虑不同的实现,例如随机选择任何免费的刷新器,或者选择最少的,或者挑选最多的,等等。

好的,下面是我的解决方案,可以非常准确地对描述建模:

代码语言:javascript
复制
interface ITicket {}

interface ICallHandler {
    boolean isAvailable();
    boolean canHandle(ITicket ticket);
}

interface ICallHandlerPicker {
    ICallHandler getAvailableCallHandler();
}

interface ICallCenter {
    ICallHandler getCallHandler(ITicket ticket);
}

class SingleLeadSingleManagerCallCenter implements ICallCenter {
    private final ICallHandlerPicker picker;
    private final ICallHandler lead;
    private final ICallHandler manager;

    SingleLeadSingleManagerCallCenter(ICallHandlerPicker picker, ICallHandler lead, ICallHandler manager) {
        this.picker = picker;
        this.lead = lead;
        this.manager = manager;
    }

    @Override
    public ICallHandler getCallHandler(ITicket ticket) {
        ICallHandler handler = picker.getAvailableCallHandler();
        if (handler == null) {
            // nobody available. perhaps throw new NoSuchElementException() ?
            return null;
        }
        if (handler.canHandle(ticket)) {
            return handler;
        }
        if (lead.isAvailable() && lead.canHandle(ticket)) {
            return lead;
        }
        return manager;
    }
}

这将附着在描述的定义良好的部分上,并使未定义的部分不被故意实现,例如:

  • 如果有很多免费的新生,你可以选择哪一个?->按你的意愿来实现。
  • 如果没有免费的新生,怎么办?-> null意味着没有人要处理,虽然我很不喜欢这个部分

其他要注意的事项:

  • 接口很短,而且很关键,只有getter,没有变体器。
  • 类成员都是最终的,而且类在构建时已经完全定义了,没有猜测的余地了
  • 在处理调用方面,技术主管和产品经理就像新生一样是调用处理程序,所以使用接口是很有意义的,不需要给他们专门的类。

当然,这是不可伸缩的。描述本身通过指定单个技术主管和单个产品经理排除了可伸缩性。我会通过将多个级别建模为呼叫中心链来解决这个问题:

代码语言:javascript
复制
class MultiLevelCallCenter implements ICallCenter {
    private final ICallHandlerPicker picker;
    private final ICallCenter nextCallCenter;

    MultiLevelCallCenter(ICallHandlerPicker picker, ICallCenter nextCallCenter) {
        this.picker = picker;
        this.nextCallCenter = nextCallCenter;
    }

    @Override
    public ICallHandler getCallHandler(ITicket ticket) {
        ICallHandler handler = picker.getAvailableCallHandler();
        if (handler == null) {
            // nobody available. perhaps throw new NoSuchElementException() ?
            return null;
        }
        if (handler.canHandle(ticket)) {
            return handler;
        }
        return nextCallCenter.getCallHandler(ticket);
    }
}

class UltimateCallCenter implements ICallCenter {
    private final ICallHandler handler;

    UltimateCallCenter(ICallHandler handler) {
        this.handler = handler;
    }

    @Override
    public ICallHandler getCallHandler(ITicket ticket) {
        return handler.isAvailable() ? handler : null;
    }
}

然后,我们可以将这些更可伸缩的类描述中的呼叫中心实现为:

代码语言:javascript
复制
ICallCenter getSingleLeadSingleManagerCallCenter(ICallHandlerPicker picker, final ICallHandler lead, ICallHandler manager) {
    ICallCenter managerCallCenter = new UltimateCallCenter(manager);
    ICallCenter leadCallCenter = new MultiLevelCallCenter(new ICallHandlerPicker() {
        @Override
        public ICallHandler getAvailableCallHandler() {
            return lead.isAvailable() ? lead : null;
        }
    }, managerCallCenter);
    return new MultiLevelCallCenter(picker, leadCallCenter);
}

再一次,我忽略了新生的细节,以及我们如何挑选他们。这些都没有在描述中指定,也没有真正的相关性。这使您可以随意注入任何您喜欢的实现。

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/43087

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档