5
\$\begingroup\$

This is a coding interview I found online, to be solved within 30 minutes:

Design an OOP concept for an application where employee can dispatch their incoming phone call according to their seniority level if they are not able to solve.

I chose to use Chain of Responsibility pattern, I also used Null pattern. Please review OOP principles and the specific design pattern implementation.

The unit test is just a code sample.

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace DesignPatternsQuestions
{
    [TestClass]
    public class ChainOfResponsibilityTest
    {
        [TestMethod]
        public void PhoneCallDispatchCORTest()
        {
            PhoneCallHandler gilad = new PhoneCallHandler(new LowLevel("Gilad"));
            PhoneCallHandler bonno = new PhoneCallHandler(new LowLevel("Bonno"));
            PhoneCallHandler batel = new PhoneCallHandler(new MediumLevel("Batel"));
            PhoneCallHandler daniel = new PhoneCallHandler(new HighLevel("Daniel"));
            gilad.RegisterNext(batel);
            bonno.RegisterNext(batel);
            batel.RegisterNext(daniel);

            BankPhoneCall call1 = new BankPhoneCall(1500);
            bool res = gilad.GetPhoneCall(call1);
            Assert.IsTrue(res);
        }
    }

    public interface IPhoneCall
    {
        int Budget { get; set; }
    }

    public class BankPhoneCall :IPhoneCall
    {

        public BankPhoneCall(int budget)
        {
            Budget = budget;
        }

        public int Budget { get; set; }
    }

    public interface IPhoneCallHandler
    {
        bool GetPhoneCall(IPhoneCall phoneCall);
        //the same item is able to call next Item in the chain of command
        void RegisterNext(IPhoneCallHandler employee);
    }

    public class PhoneCallHandler : IPhoneCallHandler
    {
        private IPhoneCallHandler _nextCallHandler = EndOfChainHandler.Instance;
        private Employee _employee;

        public PhoneCallHandler(Employee employee)
        {
            _employee = employee;
        }
        public bool GetPhoneCall(IPhoneCall phoneCall)
        {
            bool res = _employee.Resolve(phoneCall);
            if (!res)
            {
                //handle _nextcallHandler == null with null pattern
                return _nextCallHandler.GetPhoneCall(phoneCall);
            }
            return res;
        }

        public void RegisterNext(IPhoneCallHandler nextCallHandler)
        {
            _nextCallHandler = nextCallHandler;
        }
    }


    /// <summary>
    /// null pattern
    /// </summary>
    public sealed class EndOfChainHandler : IPhoneCallHandler
    {
        private static readonly Lazy<EndOfChainHandler> lazy = new Lazy<EndOfChainHandler>(()=> new EndOfChainHandler());
        public static EndOfChainHandler Instance { get { return lazy.Value; } }
        public bool GetPhoneCall(IPhoneCall phoneCall)
        {
            return false;
        }

        public void RegisterNext(IPhoneCallHandler employee)
        {
            throw  new InvalidOperationException("can't register next to null");
        }
    }

    public abstract class Employee
    {
        protected int _maxBudget;

        public string Name { get;set; }
        public Employee(string name, int maxBudget)
        {
            _maxBudget = maxBudget;
            Name = name;
        }

        public bool Resolve(IPhoneCall phoneCall)
        {
            return phoneCall.Budget <= _maxBudget;
        }
    }
    public class LowLevel : Employee
    {
        public LowLevel(string name)
            : base(name, 1000)
        {
        }
    }

    public class MediumLevel : Employee
    {
        public MediumLevel(string name)
            : base(name, 5000)
        {
        }
    }

    public class HighLevel : Employee
    {
        public HighLevel(string name)
            : base(name, 10000)
        {
        }
    }
}
\$\endgroup\$
6
\$\begingroup\$

Review

Well done, there isn't much I would change in this implementation. I would grant you the humble badge for putting yourself as LowLevel employee :)

The lazy and null pattern are well implemented (except a small issue that could possibly introduce null -> see minor issues). The chain of responsibility looks to be by the book. You've even used a visitor pattern between the handler and the employee. Each specific employee yields different behavior handling a phone call.

Minor issues

  • IPhoneCall as interface seems overkill to me. This is just a POCO.
  • Check arguments against null in public entry points such as public PhoneCallHandler(Employee employee).
  • GetPhoneCall could be written more compact: return _employee.Resolve(phoneCall) || _nextCallHandler.GetPhoneCall(phoneCall);.
  • RegisterNext should not allow null, as this bypasses your EndOfChainHandler.Instance pattern to avoid null handling.
\$\endgroup\$
  • 2
    \$\begingroup\$ You rock man, thanks for the amazing reviews \$\endgroup\$ – Gilad Sep 4 '19 at 17:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.