কম্যান্ড ডিজাইন প্যাটার্ন (Command Design Pattern)

প্রথমেই একটু নিজের কথা বলি । এই কমান্ড প্যাটার্ন মহাশয়কে আমার কাছে বেশ ঘাড় ত্যাড়া ধরণের মনে হয়েছে । আমার বুঝতে একটু ঝামেলা হচ্ছিলো । গফ মামাদের বই পড়লাম কিচ্ছু বুঝলাম না। মারটিন ফাওলার মামার বই পড়ে কিছু মিছু বুঝতে সক্ষম হইলাম। সবশেষে আমাদের সবার জাতীয় মামা  গুগল মামার কাছ থেকেই আসল জিনিসটা বুঝে নিলুম ।

এখন চলেন আমরা সফটওয়্যার ইঞ্জিনিয়ারদের টেকাটুকার হিসাব পাতির বিষয়টা ক্যাম্নে  কমান্ড প্যাটার্নে ফেলা যায় তা নিয়া একটা জ্ঞানগর্ভ আলোচনা করি 🙂

আমরা প্রথমে সফটওয়্যার ইঞ্জিনিয়ারদের পদবীগুলো বানিয়ে ফেলি এইভাবেঃ

    public enum Employee
    {
       Trainee,
       JuniorSoftwareEngineer,
       SeniorSoftwareEngineer,
       ProjectManager
    }

এরপর আমরা এই সফটওয়্যার ইঞ্জিনিয়ারদের স্যালারী হিসেব করে ফেলিঃ

public class SalaryCalculator
    {
        public double CalculateSalary(Employee employee)
        {
            var salary = 0.0;
            const double baseSalary = 40000;
            switch (employee)
            {
                case Employee.Trainee:
                    salary = baseSalary / 2;    //Any other business logic goes here
                    break;
                case Employee.JuniorSoftwareEngineer:
                    salary = baseSalary;        //Any other business logic goes here
                    break;
                case Employee.SeniorSoftwareEngineer:
                    salary = baseSalary * 2;    //Any other business logic goes here
                    break;
                case Employee.ProjectManager:
                    salary = baseSalary * 3;    //Any other business logic goes here
                    break;
            }

            return salary;
        }
    }

এরপর আমরা আমাদের ক্লায়েন্ট কোড এ SalaryCalculator class কে ব্যবহার করবো এভাবেঃ

public class Program
    {
        public static void Main(string[] args)
        {
            var caclculator = new SalaryCalculator();
            var salary = caclculator.CalculateSalary(Employee.SeniorSoftwareEngineer);
            Console.WriteLine(salary);
        }
    }

এখানে যে স্যালারী দেখানো হয়েছে তা শুধুমাত্র উদাহরণের খাতিরে দেখানো হয়েছে। আসলে স্যালারী অনেক কিছুর উপর নির্ভর করে। যেমন পারফর্মেন্স, অভিজ্ঞতা ইত্যাদি । কেউ ভুলেও ধরে নিয়েন না যে এ গুলোই আসল স্যালারী 😛

এখন স্যালারী ক্যালকুলেশনের  উপরোক্ত কোড পারফেক্ট কাজ করবে যেহেতু অল্প সংখ্যক Switch Case আছে। কিন্তু চিন্তা করুন এই কোডটুকু যদি একটি রিয়েল প্রোজেক্টের কোড হয় যার কাজ হচ্ছে বিশাল কোনও সফটওয়্যার ফার্মের জব হোল্ডারদের স্যালারী হিসেব করা তাহলে এই কোডের ডিজাইন একটি ব্যাড ডিজাইন ।

কারণঃ

১) যখন Switch Case অনেক বড় হয়ে যাবে তখন  কন্ডিশনাল লজিক বুঝতে অনেক সমস্যা হবে।

২) কন্ডিশনাল লজিক যখন হার্ড কোডেড থাকবে তখন নতুন কোনও বাড়তি কন্ডিশন যোগ করতে হলে এর স্যালারী ক্যালকুলেশনের লজিকটাও হার্ড কোডেড লিখে দিতে হবে যা OOP এর OCP(Open Closed Principle) এর রুল ভঙ্গ করে ।

তাহলে এর সমাধান কি? সমাধান হচ্ছে আমরা প্রথমে কন্ডিশনাল লজিক গুলো মেথড আকারে লিখবো । লিখার পর আমাদের SalaryCalculator class টি দেখতে ঠিক এরকম হবেঃ

public class SalaryCalculator
    {
        private const double BaseSalary = 40000;
        public double CalculateSalary(Employee employee)
        {
            var salary = 0.0;

            switch (employee)
            {
                case Employee.Trainee:
                    salary = CalculateTraineeSalary(BaseSalary);
                    break;
                case Employee.JuniorSoftwareEngineer:
                    salary = CalculateJrSoftwareEngineerSalary(BaseSalary);
                    break;
                case Employee.SeniorSoftwareEngineer:
                    salary = CalculateSrSoftwareEngineerSalary(BaseSalary);
                    break;
                case Employee.ProjectManager:
                    salary = CalculateProjectManagerSalary(salary);
                    break;
            }

            return salary;
        }

        private static double CalculateProjectManagerSalary(double baseSalary)
        {
            return baseSalary * 3;
        }

        private static double CalculateSrSoftwareEngineerSalary(double baseSalary)
        {
            return baseSalary * 2;
        }

        private static double CalculateJrSoftwareEngineerSalary(double baseSalary)
        {
            return baseSalary;
        }

        private static double CalculateTraineeSalary(double baseSalary)
        {
            return baseSalary / 2;
        }
    }

আর আমাদের ক্লায়েন্ট কোড ঠিক আগের মতই থাকবেঃ

public class Program
    {
        public static void Main(string[] args)
        {
            var caclculator = new SalaryCalculator();
            var salary = caclculator.CalculateSalary(Employee.SeniorSoftwareEngineer);
            Console.WriteLine(salary);
        }
    }

এখানে দেখা যাচ্ছে আমরা স্যালারী ক্যালকুলেট করার বিজনেস লজিকগুলো আলাদা আলাদা মেথডে নিয়ে গিয়েছি। উদাহরণের সুবিধার জন্য আমরা খুবই সিম্পল বিজনেস লজিক বানিয়েছি মেথড গুলোতে । কিন্তু রিয়েল এপ্লিকেশনে স্যালারী ক্যালকুলেট করার জন্য সাধারণত বেশ জটিল লজিক ইউজ করা হয় । এতে করে কিন্তু আমাদের মেথডগুলো বেশ বড় হয়ে যাবে এবং OOP এর SRP (Single Responsibility Principle) ভঙ্গ করবে কেননা যে কাজটুকু একটা class এর করার কথা সে কাজটুকু আমরা একটা মেথডের মধ্যে করে ফেলছি।

তাহলে আমরা মেথডের কাজগুলো আলাদা আলাদা class এ লিখে ফেলি। পর্যায়ক্রমে আমাদের class গুলো ঠিক এরকমঃ

১) TraineeSalaryCalculator:

public class TraineeSalaryCalculator
    {
        public double CalculateTraineeSalary(double baseSalary)
        {
            return baseSalary / 2;
        }
    }

২) JrSoftwareEngineerSalaryCalculator:

public class JrSoftwareEngineerSalaryCalculator
    {
        public double CalculateJrSoftwareEngineerSalary(double baseSalary)
        {
            return baseSalary;
        }
    }

৩) SrSoftwareEngineerSalaryCalculator:

public class SrSoftwareEngineerSalaryCalculator
    {
        public double CalculateSrSoftwareEngineerSalary(double baseSalary)
        {
            return baseSalary * 2;
        }
    }

৪) ProjectManagerSalaryCalculator:

public class ProjectManagerSalaryCalculator
    {
        public double CalculateProjectManagerSalary(double baseSalary)
        {
            return baseSalary * 3;
        }
    }

সবশেষে আমাদের SalaryCalculator class টি দাঁড়াল গিয়ে এরকমঃ

public class SalaryCalculator
    {
        private const double BaseSalary = 40000;

        private readonly TraineeSalaryCalculator _traineeSalaryCalculator;
        private readonly JrSoftwareEngineerSalaryCalculator _jrSoftwareEngineerSalaryCalculator;
        private readonly SrSoftwareEngineerSalaryCalculator _srSoftwareEngineerSalaryCalculator;
        private readonly ProjectManagerSalaryCalculator _projectManagerSalaryCalculator;

        public SalaryCalculator()
        {
            _traineeSalaryCalculator = new TraineeSalaryCalculator();
            _jrSoftwareEngineerSalaryCalculator = new JrSoftwareEngineerSalaryCalculator();
            _srSoftwareEngineerSalaryCalculator = new SrSoftwareEngineerSalaryCalculator();
            _projectManagerSalaryCalculator = new ProjectManagerSalaryCalculator();
        }
        public double CalculateSalary(Employee employee)
        {
            var salary = 0.0;

            switch (employee)
            {
                case Employee.Trainee:
                    salary = _traineeSalaryCalculator.CalculateTraineeSalary(BaseSalary);
                    break;
                case Employee.JuniorSoftwareEngineer:
                    salary = _jrSoftwareEngineerSalaryCalculator.CalculateJrSoftwareEngineerSalary(BaseSalary);
                    break;
                case Employee.SeniorSoftwareEngineer:
                    salary = _srSoftwareEngineerSalaryCalculator.CalculateSrSoftwareEngineerSalary(BaseSalary);
                    break;
                case Employee.ProjectManager:
                    salary = _projectManagerSalaryCalculator.CalculateProjectManagerSalary(BaseSalary);
                    break;
            }

            return salary;
        }

    }

লক্ষণীয় ব্যাপার হচ্ছে আমরা কিন্তু এখনও আমাদের ক্লায়েন্ট কোডে কোনও পরিবর্তন আনিনি । আগের মতই আছে আমাদের ক্লায়েন্ট কোড।

এখন আমরা দেখতে পাচ্ছি যে সবগুলো class ই একই রকমের কাজ করছে । সবাই শুধুমাত্র ক্যালকুলেশনের কাজই করছে। তাহলে আমরা এখন একটি কমন একটা ইন্টারফেস বানাতে পারি  যাকে সবাই ব্যবহার করে তাদের নিজ নিজ কাজটুকু করে নিবে। এতে আমাদের কোড ডুপ্লিকেশন কমে যাবে।

আমাদের ইন্টারফেসটি হবে এ রকমঃ

    interface ISalaryCalculator
    {
        double Calculate(double baseSalary);
    }

যাকে যথাক্রমে TraineeSalaryCalculator, JrSoftwareEngineerSalaryCalculator, SrSoftwareEngineerSalaryCalculator এবং ProjectManagerSalaryCalculator class গুলো ইমপ্লিমেন্ট করবে । ইমপ্লিমেন্ট করার পর class গুলোর চেহারা যথাক্রমে এরকম দেখাবেঃ

  1. TraineeSalaryCalculator:
    public class TraineeSalaryCalculator : ISalaryCalculator
        {
            public double Calculate(double baseSalary)
            {
                return CalculateTraineeSalary(baseSalary);
            }
    
            private static double CalculateTraineeSalary(double baseSalary)
            {
                return baseSalary / 2;
            }
        }
  2. JrSoftwareEngineerSalaryCalculator:
    public class JrSoftwareEngineerSalaryCalculator : ISalaryCalculator
        {
    
            public double Calculate(double baseSalary)
            {
                return CalculateJrSoftwareEngineerSalary(baseSalary);
            }
            private static double CalculateJrSoftwareEngineerSalary(double baseSalary)
            {
                return baseSalary;
            }
    
        }
  3. SrSoftwareEngineerSalaryCalculator:
    public class SrSoftwareEngineerSalaryCalculator : ISalaryCalculator
        {
    
            public double Calculate(double baseSalary)
            {
                return CalculateSrSoftwareEngineerSalary(baseSalary);
            }
            private static double CalculateSrSoftwareEngineerSalary(double baseSalary)
            {
                return baseSalary * 2;
            }
        }
  4. ProjectManagerSalaryCalculator:
    public class ProjectManagerSalaryCalculator : ISalaryCalculator
        {
            public double Calculate(double baseSalary)
            {
                return CalculateProjectManagerSalary(baseSalary);
            }
    
            private static double CalculateProjectManagerSalary(double baseSalary)
            {
                return baseSalary * 3;
            }
        }

সবশেষে আমাদের SalaryCalculator class টা গিয়ে দাঁড়াবে এরকমঃ

public class SalaryCalculator
    {
        public double BaseSalary { get; set; }

        private ISalaryCalculator _salaryCalculator;

        public SalaryCalculator()
        {
            BaseSalary = 40000;
        }
        public double CalculateSalary(Employee employee)
        {
            var salary = 0.0;

            switch (employee)
            {
                case Employee.Trainee:
                    _salaryCalculator = new TraineeSalaryCalculator();
                    salary = _salaryCalculator.Calculate(BaseSalary);
                    break;
                case Employee.JuniorSoftwareEngineer:
                    _salaryCalculator = new JrSoftwareEngineerSalaryCalculator();
                    salary = _salaryCalculator.Calculate(BaseSalary);
                    break;
                case Employee.SeniorSoftwareEngineer:
                    _salaryCalculator = new SrSoftwareEngineerSalaryCalculator();
                    salary = _salaryCalculator.Calculate(BaseSalary);
                    break;
                case Employee.ProjectManager:

                    _salaryCalculator = new ProjectManagerSalaryCalculator();
                    salary = _salaryCalculator.Calculate(BaseSalary);
                    break;
            }

            return salary;
        }

    }

এখন পর্যন্ত কিন্তু আমাদের ক্লায়েন্ট কোড আগের মতই থাকবে । এখন একটু আলোচনা 🙂

কম্যান্ড প্যাটার্ন অনুসারে প্রতিটি কম্যান্ড/অপারেশন হবে একেকটি অবজেক্ট । একটি কম্যান্ড যে অবজেক্টের উপর কাজ করবে তাকে বলা হবে Receiver । অন্যদিকে Invoker এর কাছে Command এর একটি Queue/List থাকবে এবং কম্যান্ডগুলো একের পর এক এক্সিকিউট করবে ।

আমাদের চার ধরণের ক্যালকুলেশন গুলো যে class গুলো করেছে সেগুলো হল Receiver, SalaryCalculator class হচ্ছে এখানে আমাদের Invoker class  এবং ISalaryCalculator হচ্ছে আমাদের কম্যান্ড class.

তাহলে কম্যান্ড প্যাটার্ন অনুযায়ী আমরা আমাদের Invoker class এর ডিজাইন চেঞ্জ করে ফেলি:

public class SalaryCalculator
    {
        private double BaseSalary { get; set; }
        private Stack<ISalaryCalculator> _salaryCalculator = new Stack<ISalaryCalculator>();

        public SalaryCalculator()
        {
            BaseSalary = 40000;
        }
        public void CalculateSalary()
        {
            ISalaryCalculator calculator = _salaryCalculator.Pop();
            double salary = calculator.Calculate(BaseSalary);
            Console.WriteLine(salary);
        }

        public void AddSalaryCalculator(ISalaryCalculator salaryCalculator)
        {
            _salaryCalculator.Push(salaryCalculator);
        }
    }

এখানে প্রত্যেকটি কম্যান্ড অব্জেক্ট ধরে রাখার জন্য আমরা একটি Stack একটি ইউজ করেছি এবং AddSalaryCalculator  মেথডের মধ্য দিয়ে আমরা ISalaryCalculator প্যারামিটার দিয়ে দিয়েছি এবং এটিকে Stack এ পুশ করেছি যাতে করে আমরা আমাদের ক্লায়েন্ট কোড থেকে নতুন নতুন Receiver অবজেক্ট যোগ করতে পারি ।

সবশেষে CalculateSalary এর মধ্যে আমরা Stack থেকে সদ্য যোগ করা কম্যান্ড অবজেক্ট থেকে স্যালারী ক্যালকুলেট করে প্রিন্ট করে দিয়েছি।

সবশেষে আমরা CalculateSalary মেথডের মধ্যে Dictionary থেকে Requested Key দিয়ে ম্যাপ করে এর অবজেক্ট নিয়ে আমরা এর Calculate মেথড কল করে স্যালারী রিটার্ন করেছি। আমাদের ক্লায়েন্ট কোড শেষ বারের মত দেখে নেয়া যাকঃ

public class Program
    {
        public static void Main(string[] args)
        {
            var salaryCalculator = new SalaryCalculator();//invoker

            salaryCalculator.AddSalaryCalculator(new JrSoftwareEngineerSalaryCalculator());
            salaryCalculator.CalculateSalary();

            salaryCalculator.AddSalaryCalculator(new ProjectManagerSalaryCalculator());
            salaryCalculator.CalculateSalary();

        }
    }

এখন কেউ যদি এই কোড দিয়ে নতুন কোনও এমপ্লয়ীর স্যালারী ক্যালকুলেট করতে যায় তবে তাকে নতুন এমপ্লয়ীর জন্য একটি class বানাতে হবে যেটি ISalaryCalculator কে ইমপ্লিমেন্ট করবে  এবং সেটিকে SalaryCalculator এর AddSalaryCalculator মেথডের মধ্য ঢুকিয়ে দিলেই নতুন এপ্লয়ির স্যালারী প্রিন্ট করে দেখাবে । একাজ গুলোর কোনটাই আমাদের OOP এর OCP(Open Closed Principle) ভাঙ্গবে না কারণ আমরা ফাংশনালিটি এক্সটেন্ড করেছি কিন্তু মোডিফিকেশন করিনি।

এতক্ষণ যে আমরা টেকাটুকার হিসাব পাতি করলাম এইগুলান সবই হচ্ছে কম্যান্ড প্যাটার্নের জন্যি।

যাই হোক হ্যাপি কোডিং 🙂