Step by step design from HLD to LLD: set -1


Note : - There is no straight forward answers to any design questions. There can be n number of solutions of any problem, discuss the trade-offs of different variations, your interviewer may twist the requirement to see how you are approaching to solve it. There is no right and wrong answer to any design question, it just depends, how well you are able to present your design and how good you are in tackling different alternatives. Each alternative design have their own plus and minuses, you just have to sense your interviewer's interest, what he is looking. So talk to him/her. Design interviews has to be mutually executed, you cannot derive it alone. Covering all aspect would be difficult, just come up with one or two main working flows and demonstrate in your design.

Please go through how to handle software design questions, if you have not read it, before you read further.
  • Problem statement
    • Design a call center. List out the classes and data structure you will use to design it.
  • Step-1 (Understand the requirements)
    • As we all know, design a call center is very abstract statement. Before we think about solution, we should ask many questions to understand problem statement.
    • Ask questions -
      • You may ask - is it a domestic call center or international? To get clarity whether you have to support English language or native languages as well. 
        • Answer - to make it simple, think we have to support only one communication language.
      • Question 2 - how many different levels of employee, call center has? How escalation system works? To understand the business logic, which you are going to design.
        • Answer - lets imagine, there are 3 different levels of employees 
          • Respondent
          • Manager
          • Director
        • Incoming telephone call will go to respondent who is free. If all respondent are busy and no one is attending the call, call must escalate to manager, if all managers are busy and not attending the call then it must get escalated to director.
    • Draw system boundary -
      • This is very important. You should know, what your system/application supposed to do, and what not to do. Knowing what not to do, will help us focusing only on what we are supposed to do. Try to make system follow black box design principle.
      • Here functional and non-function requirement comes into the picture.
      • Draw a rectangular boundary and specify the functional areas of your system/application.
    • Draw use case diagram -
      • Drawing use case diagram, and demonstrating the flow with interviewer will make sure that you both are in same page, if not then ask more questions until you get full clarity of problem statement.
      • Look at below mentioned use case diagram, which is demonstrating the requirement of problem which we have discussed above - 
    • Draw activity diagram -
      • Drawing activity diagram, also comes under modeling the requirements. So in this step you will elaborate use case diagrams and create activity diagram for that particular use case, with this you will understand the requirement better.
      • Through activity diagram you will understand business logic in detail. It detail out sequence of events.
      • Look at below mentioned activity diagram, which is demonstrating the business logic flow of problem which we have discussed above - 
  • Step-2 (Identify classes)
    • After you have completed step-1, you can actually identify different classes. You can classify different category for that, like mentioned below - 
      • Boundary classes (like front controller classes, or say interface classes)
      • Entity classes (like - attending call)
      • Control classes (like - handling escalations and logging them, control classes sometimes also manage how entity classes supposed to interact)
    • At this stage list out the general behavior (APIs) of classes. At this point it need not to be final set of APIs or fully mature class, just a high level understanding, of what all operation this class will perform and how you come up with these classes.
    • In particulate, for call center, we can think of few classes from activity and use case diagrams, like -
      • Caller, there has to be a caller. Caller is actor in use case diagram.
      • CallHandler, this class will be responsible for attending the call.
      • Employee, this will be abstract class, as we can figure out that we are having 3 different levels of employee
        • Director, this class will inherit Employee class.
        • Manager, this class will inherit Employee class.
        • Respondent, this class will inherit Employee class.
  • Step-3 (create communication diagram)
    • Ideally you should create communication diagram, to get more grip on how identified classes will interact to each other.
    • If by now, you are not clear on requirement, you should ask many questions, on identified classes (like understand interviewer's view on identified classes, or is that what he wanted, the system to look like?).
    • Tell the role of each individual class.
    • If you both are conflicting. Draw communication diagram.
    • If interviewer is kind of agree with you. Then you can actually skip creating communication diagram and move to step-4 (sequence diagram).  
  • Step-4 (create sequence diagram)
    • After step-2 you have classes. Now the next task is, how those classes will interact. Why you should not jump into, cratering fully functional class diagram, before completing sequence diagram?
      • While exercising sequence diagram, and thinking how classes will interact to each-other, you may feel that you need some extra classes. Yes here I a talking about different helper classes. You may need many helper classes, apart from direct identified classes to make system work smoothly.
    • Look at below mentioned sequence diagram, which will demonstrate the flow sequence of call center classes -
    •  
  • step-5(create class diagram)
    • In this step we can create fully interactive class diagram, as we know how they will interact and in which sequence.
    • Please look at below mentioned class diagram, for our problem statement (call center). 
  • Step-6(Write code)
    • Believe me, if you have followed all above mentioned steps properly, you will write code with confidence and there will be very less space of mistake.
    • Note - you may feel that there could be better way of writing the code or solution, and answer is yes there is! my intention is to take you from very abstract level to concrete implementation, not giving production standard solution.
    • Note : - Compile this code in c++ 11 (command - g++ -std=c++11 CallCenter.cpp) 
      #include<iostream>
      #include<map>
      #include <memory> // to include all smart pointers
      using namespace std;
      
      //--------------------------------------------------------------------------------------------------
      // Employee hierarchy
      //--------------------------------------------------------------------------------------------------
      enum Rank
      {
          DIRECTOR,
          MANAGER,
          RESPONDENT
      };
      class Call;
      //--------------------------------------------------------------------------------------------------
      class Employee
      {
      public:
          typedef shared_ptr<Employee> Ptr;
          typedef shared_ptr<const Employee> ConstPtr;
          virtual void receiveCall(shared_ptr<Call> call) = 0;
          // resolve the query and cut the call.
          virtual void callCompleted() = 0;
          virtual bool isFree() = 0;
          virtual Rank getRank() = 0;
          virtual void makeBusyForTestOnly() = 0;
          virtual ~Employee() {}
      private:
          shared_ptr<Call> m_currentCall;
      };
      //--------------------------------------------------------------------------------------------------
      class Director : public Employee
      {
      public:
          Director(int empId) : m_rank(DIRECTOR), m_empID(empId), m_busy(false) {}
          void receiveCall(shared_ptr<Call> call)
          {
              cout << "Director is Director the call" << endl;
              m_busy = true;
              callCompleted();
          }
          void callCompleted()
          {
              cout << "Director is Director the call" << endl;
              m_busy = false;
          }
          bool isFree()
          {
              return !m_busy;
          }
          Rank getRank()
          {
              return m_rank;
          }
          void makeBusyForTestOnly()
          {
              m_busy = true;
          }
      private:
          Rank m_rank;
          int m_empID;
          bool m_busy;
      };
      //--------------------------------------------------------------------------------------------------
      class Manager : public Employee
      {
      public:
          Manager(int empId) : m_rank(MANAGER), m_empID(empId), m_busy(false) {}
          void receiveCall(shared_ptr<Call> call)
          {
              cout << "Manager is receiving the call" << endl;
              m_busy = true;
              callCompleted();
          }
          void callCompleted()
          {
              cout << "Manager is completing the call" << endl;
              m_busy = false;
          }
          bool isFree()
          {
              return !m_busy;
          }
          Rank getRank()
          {
              return m_rank;
          }
          void makeBusyForTestOnly()
          {
              m_busy = true;
          }
      private:
          Rank m_rank;
          int m_empID;
          bool m_busy;
      };
      //--------------------------------------------------------------------------------------------------
      class Respondent : public Employee
      {
      public:
          Respondent(int empId) : m_rank(RESPONDENT), m_empID(empId), m_busy(false){}
          void receiveCall(shared_ptr<Call> call)
          {
              cout << "Respondent is receiving the call" << endl;
              m_busy = true;
              callCompleted();
          }
          void callCompleted()
          {
              cout << "Respondent is completing the call" << endl;
              m_busy = false;
          }
          bool isFree()
          {
              return !m_busy;
          }
          Rank getRank()
          {
              return m_rank;
          }
          void makeBusyForTestOnly()
          {
              m_busy = true;
          }
      private:
          Rank m_rank;
          int m_empID;
          bool m_busy;
      };
      //--------------------------------------------------------------------------------------------------
      class EmployeeFactory
      {
      public:
          static shared_ptr<Employee> employee(Rank rank, int empId)
          {
              if (rank == DIRECTOR)
              {
                  return make_shared<Director>(empId);
              }
              else if (rank == MANAGER)
              {
                  return make_shared<Manager>(empId);
              }
              else if (rank == RESPONDENT)
              {
                  return make_shared<Respondent>(empId);
              }
              else
              {
                  cout << "Invalid argument ";
                  return nullptr;
              }
          }
      };
      //--------------------------------------------------------------------------------------------------
      // classes related to call and how we will handle it.
      //--------------------------------------------------------------------------------------------------
      class Caller;
      class callHandler;
      //--------------------------------------------------------------------------------------------------
      class Call : public std::enable_shared_from_this<Call>
      {
      public:
          typedef shared_ptr<Call> Ptr;
      
          static shared_ptr<Call> create(shared_ptr<Caller> caller)
          {
              return make_shared<Call>(caller);
          }
          void assign();
          Call(shared_ptr<Caller> caller) : m_caller(caller)
          { }
      private:
          shared_ptr<Caller> m_caller;
      
      };
      //--------------------------------------------------------------------------------------------------
      class Caller : public std::enable_shared_from_this<Caller> // note: public inheritance
      {
      public:
          static shared_ptr<Caller> create(const string& name, const string& number)
          {
              return make_shared<Caller>(name, number);
          }
          Caller(const string& name, const string& number) : m_name(name), m_number(number) {}
          void call();
      private:
          const string m_name;
          const string m_number;
      };
      //--------------------------------------------------------------------------------------------------
      class CallHandler
      {
      public:
          //friend class CallHandlerFactory;
          void handleCall(shared_ptr<Call> call)
          {
              int empIndex = 0;
              for (; empIndex < 10; ++empIndex)
              {
                  RespondentsRoundRobin += 1;
                  if (RespondentsRoundRobin == 111)
                  {
                      RespondentsRoundRobin = 101;
                  }
                  if (m_employees[RespondentsRoundRobin]->isFree())
                  {
                      assignCall(m_employees[RespondentsRoundRobin], call);
                      return;
                  }
              }
              if (empIndex == 10)
              {
                  cout << "All Respondents are busy" << endl;
              }
              else
              {
                  return;
              }
              empIndex = 0;
              for (; empIndex < 4; ++empIndex)
              {
                  MgrsRoundRobin += 1;
                  if (MgrsRoundRobin == 1005)
                  {
                      MgrsRoundRobin = 1001;
                  }
                  if (m_employees[MgrsRoundRobin]->isFree())
                  {
                      assignCall(m_employees[MgrsRoundRobin], call);
                      return;
                  }
              }
              if (empIndex == 4)
              {
                  cout << "All Managers are busy" << endl;
              }
              else
              {
                  return;
              }
              empIndex = 0;
              for (; empIndex < 2; ++empIndex)
              {
                  DirectorsRoundRobin += 1;
                  if (DirectorsRoundRobin == 10003)
                  {
                      DirectorsRoundRobin = 10001;
                  }
                  if (m_employees[DirectorsRoundRobin]->isFree())
                  {
                      assignCall(m_employees[DirectorsRoundRobin], call);
                      return;
                  }
              }
              if (empIndex == 2)
              {
                  cout << "All Directors are busy" << endl;
              }
          }
          void assignCall(shared_ptr<Employee> emp, shared_ptr<Call> call)
          {
              emp->receiveCall(call);
          }
          static shared_ptr<CallHandler> create()
          {
              return make_shared<CallHandler>();
          }
          CallHandler() : m_levels(3),
              m_numbersOfRespondents(10),
              m_numbersOfMgrs(4),
              m_numbersOfDirectors(2)
          {
              // list of RESPONDENT, empId starts from 101
              m_employees[101] = EmployeeFactory::employee(RESPONDENT, 101);
              m_employees[102] = EmployeeFactory::employee(RESPONDENT, 102);
              m_employees[103] = EmployeeFactory::employee(RESPONDENT, 103);
              m_employees[104] = EmployeeFactory::employee(RESPONDENT, 104);
              m_employees[105] = EmployeeFactory::employee(RESPONDENT, 105);
              m_employees[106] = EmployeeFactory::employee(RESPONDENT, 106);
              m_employees[107] = EmployeeFactory::employee(RESPONDENT, 107);
              m_employees[108] = EmployeeFactory::employee(RESPONDENT, 108);
              m_employees[109] = EmployeeFactory::employee(RESPONDENT, 109);
              m_employees[110] = EmployeeFactory::employee(RESPONDENT, 110);
              // list of MANAGER, empId starts from 1001
              m_employees[1001] = EmployeeFactory::employee(MANAGER, 1001);
              m_employees[1002] = EmployeeFactory::employee(MANAGER, 1002);
              m_employees[1003] = EmployeeFactory::employee(MANAGER, 1003);
              m_employees[1004] = EmployeeFactory::employee(MANAGER, 1004);
              // list of DIRECTOR, empId starts from 10001
              m_employees[10001] = EmployeeFactory::employee(DIRECTOR, 10001);
              m_employees[10002] = EmployeeFactory::employee(DIRECTOR, 10002);
          }
          // for test pourpose only.
          friend void makeRespondemtBusyTest();
          friend void makeMngrBusyTest();
      private:
          int m_levels;
          int m_numbersOfRespondents;
          int m_numbersOfMgrs;
          int m_numbersOfDirectors;
          // list of employees
          map<int, shared_ptr<Employee> > m_employees;
          // list of call queue
          static int RespondentsRoundRobin;
          static int MgrsRoundRobin;
          static int DirectorsRoundRobin;
      
      };
      int CallHandler::RespondentsRoundRobin = 100;
      int CallHandler::MgrsRoundRobin = 1000;
      int CallHandler::DirectorsRoundRobin = 10000;
      //--------------------------------------------------------------------------------------------------
      class CallHandlerFactory
      {
      public:
          static void initialize()
          {
              if (m_callHandler == nullptr)
              {
                  m_callHandler = CallHandler::create();
              }
          }
          static shared_ptr<CallHandler> callHandler()
          {
              if (m_callHandler == nullptr)
              {
                  CallHandlerFactory::initialize();
              }
              else
              {
                  return m_callHandler;
              }
          }
      private:
          static shared_ptr<CallHandler> m_callHandler;
      };
      shared_ptr<CallHandler> CallHandlerFactory::m_callHandler;
      //--------------------------------------------------------------------------------------------------
      void Caller::call()
      {
          shared_ptr<Call> call = Call::create(shared_from_this());
          call->assign();
      }
      //--------------------------------------------------------------------------------------------------
      void Call::assign()
      {
          CallHandlerFactory::callHandler()->handleCall(shared_from_this());
      }
      //--------------------------------------------------------------------------------------------------
      /*
      
      */
      void doConfiguration()
      {
          CallHandlerFactory::initialize();
      }
      //--------------------------------------------------------------------------------------------------
      void callToRespondentTest()
      {
          auto caller = Caller::create("SomeName", "123567890");
          caller->call();
      }
      void makeRespondemtBusyTest()
      {
          for (int empIndex = 101; empIndex < 111; ++empIndex)
          {
              CallHandlerFactory::callHandler()->m_employees[empIndex]->makeBusyForTestOnly();
          }
      }
      void callToManagerTest()
      {
          makeRespondemtBusyTest();
          auto caller = Caller::create("SomeName", "123567890");
          caller->call();
      }
      void makeMngrBusyTest()
      {
          for (int empIndex = 1001; empIndex < 1005; ++empIndex)
          {
              CallHandlerFactory::callHandler()->m_employees[empIndex]->makeBusyForTestOnly();
          }
      }
      void callToDirectorTest()
      {
          makeMngrBusyTest();
          auto caller = Caller::create("SomeName", "123567890");
          caller->call();
      }
      //--------------------------------------------------------------------------------------------------
      int main()
      {
          doConfiguration();
          callToRespondentTest();
          callToManagerTest();
          callToDirectorTest();
          return 0;
      }
      
  • Throughout all steps, make sure you are interactive with your interviewer, that will help you from going far in wrong direction, and having conflicts at the end on your proposed solution with interviwer.
Thanks for reading it. To read more on design patterns and basic design principles please see my web page. You can also join me on FB or on G++. Please drop comments for any question related to this blog.

Comments

Popular posts from this blog

Non-virtual interface idiom (NVI)

Architectural patterns => Mud to structure => layers.

Architectural style -> Adoptable system -> Reflection.