OOD Use Case: Solving call center problem

muhammad_salem

Muhammad Salem

Posted on July 5, 2024

OOD Use Case: Solving call center problem

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

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
muhammad_salem
Muhammad Salem

Posted on July 5, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related