OOD Use Case: Solving call center problem
Published: 2024-07-05 13:42:26


Call Center: Imagine you have a call center with three levels of employees: respondent, manager, and director. An incoming telephone call must be first allocated to a respondent who is free. If the respondent can't handle the call, he or she must escalate the call to a manager. If the manager is not free or not able to handle it, then the call should be escalated to a director. Design the classes and data structures for this problem. 

### Thought Process and Design Decisions

1. **Identify Core Entities**: The primary entities are `Call`, `Employee`, `Respondent`, `Manager`, and `Director`. Each employee can handle a call or escalate it if they are unable to handle it.

2. **Class Hierarchy**: 
    - `Employee` is the base class for `Respondent`, `Manager`, and `Director`.
    - `Call` represents an incoming call.

3. **Behavior**:
    - `Employee` has methods to `handleCall` and `escalateCall`.
    - Each `Employee` can be in one of the three states: available, busy, or on break.

4. **Data Structures**:
    - A queue to manage incoming calls.
    - Separate lists for available respondents, managers, and directors.

5. **Method `dispatchCall`**:
    - Check for the availability of respondents first, then managers, and finally directors.
    - Escalate the call if no lower-level employees are available.

### Class Design

```csharp
using System;
using System.Collections.Generic;

public enum EmployeeLevel
{
    Respondent,
    Manager,
    Director
}

public enum CallStatus
{
    Waiting,
    InProgress,
    Completed
}

public class Call
{
    public int Id { get; set; }
    public CallStatus Status { get; set; }

    public Call(int id)
    {
        Id = id;
        Status = CallStatus.Waiting;
    }
}

public abstract class Employee
{
    public int Id { get; set; }
    public EmployeeLevel Level { get; set; }
    public bool IsFree { get; set; } = true;

    public Employee(int id, EmployeeLevel level)
    {
        Id = id;
        Level = level;
    }

    public abstract void HandleCall(Call call);
}

public class Respondent : Employee
{
    public Respondent(int id) : base(id, EmployeeLevel.Respondent) { }

    public override void HandleCall(Call call)
    {
        if (IsFree)
        {
            Console.WriteLine($"Respondent {Id} is handling call {call.Id}");
            IsFree = false;
            call.Status = CallStatus.InProgress;
        }
        else
        {
            Console.WriteLine($"Respondent {Id} cannot handle call {call.Id} and needs to escalate.");
        }
    }
}

public class Manager : Employee
{
    public Manager(int id) : base(id, EmployeeLevel.Manager) { }

    public override void HandleCall(Call call)
    {
        if (IsFree)
        {
            Console.WriteLine($"Manager {Id} is handling call {call.Id}");
            IsFree = false;
            call.Status = CallStatus.InProgress;
        }
        else
        {
            Console.WriteLine($"Manager {Id} cannot handle call {call.Id} and needs to escalate.");
        }
    }
}

public class Director : Employee
{
    public Director(int id) : base(id, EmployeeLevel.Director) { }

    public override void HandleCall(Call call)
    {
        if (IsFree)
        {
            Console.WriteLine($"Director {Id} is handling call {call.Id}");
            IsFree = false;
            call.Status = CallStatus.InProgress;
        }
        else
        {
            Console.WriteLine($"Director {Id} cannot handle call {call.Id}.");
        }
    }
}

public class CallCenter
{
    private Queue<Call> callQueue = new Queue<Call>();
    private List<Respondent> respondents = new List<Respondent>();
    private List<Manager> managers = new List<Manager>();
    private List<Director> directors = new List<Director>();

    public CallCenter(int numRespondents, int numManagers, int numDirectors)
    {
        for (int i = 1; i <= numRespondents; i++)
            respondents.Add(new Respondent(i));

        for (int i = 1; i <= numManagers; i++)
            managers.Add(new Manager(i));

        for (int i = 1; i <= numDirectors; i++)
            directors.Add(new Director(i));
    }

    public void ReceiveCall(Call call)
    {
        callQueue.Enqueue(call);
        DispatchCall();
    }

    public void DispatchCall()
    {
        if (callQueue.Count == 0)
            return;

        Call call = callQueue.Dequeue();

        foreach (var respondent in respondents)
        {
            if (respondent.IsFree)
            {
                respondent.HandleCall(call);
                return;
            }
        }

        foreach (var manager in managers)
        {
            if (manager.IsFree)
            {
                manager.HandleCall(call);
                return;
            }
        }

        foreach (var director in directors)
        {
            if (director.IsFree)
            {
                director.HandleCall(call);
                return;
            }
        }

        Console.WriteLine($"No available employee to handle call {call.Id}. Adding back to queue.");
        callQueue.Enqueue(call);
    }

    public void EndCall(Call call, Employee employee)
    {
        Console.WriteLine($"Call {call.Id} completed by {employee.Level} {employee.Id}");
        call.Status = CallStatus.Completed;
        employee.IsFree = true;
        DispatchCall();
    }
}
```

### Explanation and Trade-offs

1. **Class Hierarchy**: 
   - Using inheritance (`Respondent`, `Manager`, `Director` derive from `Employee`) allows us to define shared behaviors and properties in the base class (`Employee`). This makes the code more maintainable and reduces duplication.

2. **Method Responsibility**:
   - `HandleCall` method is abstract in `Employee` and implemented by each subclass. This ensures that each type of employee can have specific handling logic if needed.
   - `DispatchCall` in `CallCenter` coordinates the allocation of calls, ensuring that calls are escalated appropriately if lower-level employees are busy.

3. **State Management**:
   - The `IsFree` property on `Employee` ensures that we can check if an employee is available to take a call. This simplifies the logic for dispatching calls.

4. **Data Structures**:
   - Using a `Queue` for calls ensures that calls are handled in the order they are received, which is typically expected in a call center.
   - Separate lists for each level of employee (`respondents`, `managers`, `directors`) allow us to easily iterate and find the first available employee at each level.

### Trade-offs and Flexibility

1. **Scalability**: 
   - The design can scale to larger numbers of employees without significant changes. Adding more respondents, managers, or directors only involves updating the initialization logic.

2. **Extensibility**:
   - Adding new types of employees (e.g., `Supervisor`) would require minimal changes. We could simply extend the `Employee` class and update the `CallCenter` dispatch logic.

3. **Separation of Concerns**:
   - The separation of responsibilities between the `CallCenter` and `Employee` classes ensures that each class has a single responsibility. This follows the Single Responsibility Principle (SRP) and makes the system easier to maintain.

4. **Complexity**:
   - The simplicity of the design may need to be balanced against the need for more complex features, such as handling priority calls or dynamically adjusting employee availability based on load. However, the current design provides a solid foundation for such extensions.

By carefully considering these design decisions and trade-offs, we can build a robust and maintainable call center system that meets the needs of both current requirements and potential future enhancements.