কম্পোজিট ডিজাইন প্যাটার্ন

মনে করুন, একজন প্রোগ্রামার BFC এর জন্যে একটি সফটওয়্যার ডেভেলপ করার জন্যে চুক্তিবদ্ধ হল । BFC এর সফটওয়্যার এর অনেক মডিউল এর মধ্যে একটি মডিউল হচ্ছে , যে BFC এর মালিক ইচ্ছে করলে কোন জোনে কত টাকার লেনদেন হয়েছে এটা ফিল্টারিং করে দেখতে পারবে । যেমন, ঢাকা শহরে ২০১৫ সালের জুন মাসে BFC এর সবগুলো আউটলেট থেকে মোট কত টাকা বেচা বিক্রি হয়েছে তা দেখতে পারবে । প্রতিটা জোনে BFC এর অনেকগুলো আউটলেট/ ষ্টোর থাকতে পারে । যেমন ঢাকা জোনে BFC এর অনেক ষ্টোর রয়েছে। এখন প্রোগ্রামারকে ঢাকা জোনের প্রতিমাসের মোট বেচাকেনার পরিমাণ বের করতে হলে তাকে প্রতিমাসে ঢাকা জোনের সবগুলো BFC এর আউটলেটের বেচাকেনার হিসেব বের করতে হবে । এজন্যে প্রোগ্রামার প্রতিটা ষ্টোরের হিসেব বের করার জন্যে ষ্টোরগুলোকে এক একটি এনটিটি হিসেবে বিবেচনা করলো । প্রতিটা ষ্টোরের একটি ইউনিক আইডি থাকবে , নাম থাকবে এবং প্রতিমাসে কতটাকা বেচাবিক্রি হয়েছে এটার জন্যে একটা প্রোপার্টি থাকবে ।

    public class Store
    {
        public int Id { get; set; }
        public String Name { get; set; }
        public int Profit { get; set; }
    }

এখন ঢাকা শহরে / জোনের মোট বেচাবিক্রির হিসেব বের করার জন্যে প্রোগ্রামার একটি City class লিখে ফেললো এভাবেঃ

    public class City
    {
        public List<Store> StoreList { get; private set; }
        public int Id { get; set; }
        public String Name { get; set; }

        public City()
        {
            StoreList = new List<Store>();
        }

        public void RemoveStore(Store store)
        {
            StoreList.Remove(store);
        }

        public void AddStore(Store store)
        {
            StoreList.Add(store);
        }

        public int GetCityProfit()
        {
            int totalProfit = 0;
            foreach (Store store in StoreList)
            {
                totalProfit += store.Profit;
            }
            return totalProfit;
        }
    }

এখানে দেখা যাচ্ছে যে , City class এ সবগুলো ষ্টোরের জন্যে একটি লিস্ট আছে ।
BFC এর নতুন কোনও আউটলেট হলে লিস্টে নতুন আউটলেট ঢোকানো অথবা কোনও আউটলেট বন্ধ হয়ে গেলে লিস্ট থেকে আউটলেট বাদ দেয়ার ব্যবস্থা রাখা হয়েছে । শেষে GetCityProfit() মেথডে City এর টোটাল প্রফিট ক্যালকুলেট করা হয়েছে । সবশেষে ক্লায়েন্ট কোডে ইউজ করা হয়েছে এভাবেঃ

    class Program
    {
        public static void Main(string[] args)
        {
            var store1 = new Store
            {
                Id = 1,
                Name = "Malibagh BFC",
                Profit = 2
            };
            var store2 = new Store
            {
                Id = 2,
                Name = "Gulshan BFC",
                Profit = 5
            };

            var city = new City
            {
                Id = 10,
                Name = "Dhaka"
            };
            city.AddStore(store1);
            city.AddStore(store2);
            int totalProfit = city.GetCityProfit();
            Console.WriteLine("Total Profit of {0} city is : {1}", city.Name, totalProfit);
            Console.ReadLine();
        }
    }

এখন মনে করুন কিছুদিনের মধ্যে BFC সারাদেশে তাদের ব্যবসা শুরু করলো । বিভাগীয় শহর এবং শহরের জেলা উপজেলা পর্যায়ে আউটলেট চালু হলো । এখন প্রোগ্রামারের উপর দায়িত্ব এসে পড়লো যে তাকে ডিভিশনেরও প্রফিট দেখাতে হবে ।উদাহরন স্বরূপ ,রাজশাহী বিভাগের প্রতিমাসে কত লাভ আসে তাও BFC এর মালিককে দেখাতে হবে । এখন বিভাগের লাভ লোকসান বের করার জন্যে প্রোগ্রামার City Class মত আরেকটি Division Class বানালো যেটি City Add/Delete করতে পারে এবং GetDivisionProfit() মেথড এ ডিভিশনের সব City এর টোটাল প্রফিট এর যোগফল বের করে । Division class দেখতে এরকমঃ

    public class Division
    {
        public List<City> CityList { get; private set; }

        public int Id { get; set; }
        public int Profit { get; set; }
        public Division()
        {
            CityList = new List<City>();
        }

        public void AddCity(City city)
        {
            CityList.Add(city);
        }

        public void RemoveCity(City city)
        {
            CityList.Remove(city);
        }

        public int GetDivisionProfit()
        {
            int totalProfit = 0;
            foreach (City city in CityList)
            {
                foreach (Store store in city.StoreList)
                {
                    totalProfit += store.Profit;
                }
            }
            return totalProfit;
        }
    }

ডিভিশনের মোট প্রফিট বের করার জন্যে ক্লায়েন্ট কোডে ডিভিশনের ব্যবহার হবে এভাবেঃ

    public class Program
    {
        public static void Main(string[] args)
        {
            var store1 = new Store
            {
                Id = 1,
                Name = "Malibagh BFC",
                Profit = 2
            };
            var store2 = new Store
            {
                Id = 2,
                Name = "Gulshan BFC",
                Profit = 5
            };

            var city = new City
            {
                Id = 10,
                Name = "Dhaka"
            };
            city.AddStore(store1);
            city.AddStore(store2);

            var division = new Division
            {
                Id = 1,
                Name = "Rajshahi"
            };

            division.AddCity(city);
            int totalProfit = division.GetDivisionProfit();
            Console.WriteLine("Total Profit of {0} division is : {1}", division.Name, totalProfit);
            Console.ReadLine();
        }
    }

প্রোগ্রামার লক্ষ্য করলো যে , প্রতি লেভেলের প্রফিট ক্যাল্কুলেশনের জন্যে তাকে প্রতিবার একটি করে লুপ বাড়াতে হচ্ছে । আবার সে চিন্তা করে দেখলো যে, এখন সে ডিভিশন লেভেলের প্রফিট ক্যাল্কুলেট করছে, যদি তাকে এশিয়া রিজিয়নের প্রফিট বের করতে বলে ( BFC যদি KFC মত ইন্টারন্যাশনাল চেইন শপ হয় কখনও) তাহলে তাকে কয়েকটা দেশের টোটাল প্রফিট বের করতে হবে । সো দেখা যাচ্ছে যে প্রতিটা লেভেলে প্রোগ্রামারকে লুপ বাড়াতে হচ্ছে এবং একটি করে Class যোগ করতে হচ্ছে । আসলে প্রোগ্রামার একটি ট্রি এর মত একটি hierarchy বানিয়ে ফেলেছে । ট্রি এর Division একটি নোড হলে City গুলো হচ্ছে Division এর চিলড্রেন ।

প্রোগ্রামার এতক্ষণ যা করেছে তা হচ্ছে ট্রি hierarchy এর প্রতিটি নোড এবং এর চাইল্ডের জন্যে একই রকম সব অপারেশন করেছে । (City এর প্রফিট এবং Division এর প্রফিট বের করার তরিকা একই, কিন্তু তাদের রিলেশন কিন্তু প্যারেন্ট -চাইল্ড ) । সো দেখা যাচ্ছে যে , প্রোগ্রামার যদি প্যারেন্ট এবং চাইল্ড কে আলাদা বিবেচনা না করে একইরকম ভাবে ট্রিট করে তাহলেই প্রবলেম সল্ভ হয়ে যাচ্ছে ।
এরকম প্রবলেম সল্ভ করার জন্যেই আসলে কম্পোজিট ডিজাইন প্যাটার্নের আবির্ভাব ।

রিফ্যাক্টোরিং টু কম্পোজিট ডিজাইন প্যাটার্নঃ
Store,City ও Division এ দেখা যাচ্ছে যে Add, Remove এবং Calculation এর কাজগুলো বার বার করা হয়েছে । তাই এগুলো কে কমন একটি ইন্টারফেস এ নিয়ে যাওয়া যায় এভাবেঃ

    public interface IProfitable
    {
        int GetProfit();
        void AddChild(IProfitable profitable);
        void RemoveChild(IProfitable profitable);
        int Id { get; set; }
        String Name { get; set; }
    }

এখন Store class একে ইমপ্লিমেন্ট করবে এভাবেঃ

    public class Store : IProfitable
    {
        public int Profit { get; set; }
        public int Id { get; set; }
        public String Name { get; set; }
        public int GetProfit()
        {
            return Profit;
        }
        public void AddChild(IProfitable profitable)
        {
            throw new NotImplementedException();
        }
        public void RemoveChild(IProfitable profitable)
        {
            throw new NotImplementedException();
        }
    }

এখানে লক্ষ্যণীয় যে Store class হচ্ছে ট্রি স্ট্রাকচারের Leaf, তাই এর AddChild এবং RemoveChild মেথড Unimplemented অবস্থায় থাকবে ।
একই ভাবে City class IProfitable কে ইমপ্লিমেন্ট করবে এভাবেঃ

    public class City : IProfitable
    {
        public int Id { get; set; }
        public String Name { get; set; }
        public List<IProfitable> StoreList { get; private set; }
        public City()
        {
            StoreList = new List<IProfitable>();

        }
        public int GetProfit()
        {
            int totalProfit = 0;

            foreach (IProfitable store in StoreList)
            {
                totalProfit += store.GetProfit();
            }

            return totalProfit;
        }

        public void AddChild(IProfitable profitable)
        {
            if (profitable is Store)
                StoreList.Add(profitable);

        }
        public void RemoveChild(IProfitable profitable)
        {
            if (profitable is Store)
                StoreList.Remove(profitable);
        }
    }

এখানে লক্ষ্যণীয় যে ,City class এর AddChild এবং RemoveChild মেথডে আমরা টাইপ চেক করে লিস্টে অবজেক্ট ঢুকিয়েছি কেননা , City class শুধুমাত্র Store অবজেক্ট নিয়ে কাজ করবে । অন্যদিকে Division class শুধুমাত্র City অবজেক্ট নিয়ে কাজ করবে এজন্যে Division class হবে এরকমঃ

    public class Division : IProfitable
    {
        public int Id { get; set; }
        public String Name { get; set; }
        public List<IProfitable> CityList { get; private set; }
        public Division()
        {
            CityList = new List<IProfitable>();
        }
        public int GetProfit()
        {
            int totalProfit = 0;
            foreach (IProfitable city in CityList)
            {
                totalProfit += city.GetProfit();
            }
            return totalProfit;
        }
        public void AddChild(IProfitable profitable)
        {
            if (profitable is City)
                CityList.Add(profitable);
        }
        public void RemoveChild(IProfitable profitable)
        {
            if (profitable is City)
                CityList.Remove(profitable);
        }
    }

City class এবং Division class থেকে দেখা যাচ্ছে যে , এই দুটো Class অনেকটা একই রকমের , তাদের মধ্যে পার্থক্য শুধুমাত্র GetProfit() মেথডে । এই মেথডে এখন আর কোনও মাল্টিপল লুপ নেই । আলাদা আলাদা Class এর GetProfit() মেথডের বিজনেস লজিক আলাদা আলাদা এবং এদের মধ্যে কোনও ডিপেন্ডেন্সি নেই । সবশেষে ক্লায়েন্ট কোডে প্রফিট ক্যালকুলেট করবে এভাবেঃ

    public class Program
    {
        public static void Main(string[] args)
        {
            IProfitable store1 = new Store
            {
                Id = 1,
                Name = "Malibagh BFC",
                Profit = 2
            };
            IProfitable store2 = new Store
            {
                Id = 2,
                Name = "Gulshan BFC",
                Profit = 5
            };

            IProfitable city = new City
            {
                Id = 10,
                Name = "Dhaka"
            };
            city.AddChild(store1);
            city.AddChild(store2);

            IProfitable division = new Division
            {
                Id = 1,
                Name = "Rajshahi"
            };
            division.AddChild(city);
            int totalProfit = division.GetProfit();
            Console.WriteLine("Total Profit of {0} division is : {1}", division.Name, totalProfit);
            Console.ReadLine();
        }
    }

সো এইছিলো আমাদের কম্পোজিট ডিজাইন প্যাটার্ন ।
হ্যাপি কোডিং 🙂

ব্রীজ ডিজাইন প্যাটার্ন

ধরুন একজন প্রোগ্রামার তার নাম যদু মিয়া গ্রাজুয়েশন শেষ করে কোনও একটি সফটওয়্যার ফার্মে প্রোগ্রামার হিসেবে তার ক্যারিয়ার স্টার্ট করলো।
অপারেটিং সিস্টেমের উপর যদু মিয়ার ভালো দক্ষতা ।যে অপারেটিং সিস্টেমের অ্যালগোরিদম গুলো বেশ ভালোভাবে রপ্ত করেছে।
তার বস যদুকে একটি প্রোজেক্টে ইনক্লুড করে দিলো, যে প্রোজেক্টে বেশ কিছু থ্রেড শিডিউলিং অ্যালগোরিদম এ যদুকে কাজ করতে হবে ।
তার বস যদুকে Round Robin অ্যালগোরিদম ইমপ্লিমেন্ট করে নিয়ে আসতে বললো । সে RoundRobin নামে একটি ক্লাস বানিয়ে win32 এপিআই ব্যবহার করে ApplyAlgorithm মেথডের ভেতরে অ্যালগোরিদম ইমপ্লিমেন্ট করে নিয়ে আসলো এভাবেঃ

   public class RoundRobin
   {
       public void ApplyAlgorithm()
       {
           Console.WriteLine("Round Robin algorithm applied here");
       }
   }

এখন যদু তার এই কোড বস কে দেখানোর আগেই বস বললো “শোন যদু ক্লায়েন্ট তার লিনাক্স মেশিনেও এই সফটওয়্যার চালাতে চায়, তুমি লিনাক্স কার্নেল এপিআই ইউজ করে অ্যালগোরিদম লিখে নিয়ে আসো” । যদু যথেষ্ট বুদ্ধিমান ছিলো সে , অবেজক্ট অরিয়েন্টেড কনসেপ্ট ইউজ করতে চাইলো ।
সে প্রথমে তার RoundRobin ক্লাসটি কে একটি ইন্টারফেস বানিয়ে নিলো এভাবেঃ

public interface IRoundRobin
{
    void ApplyAlgorithm();
 }

এরপর সে উইন্ডোজ এর জন্য ক্লাস বানালো এভাবে (যেটা IRoundRobin কে ইমপ্লিমেন্ট করে):

    public class Win32RoundRobin : IRoundRobin
    {
        public void ApplyAlgorithm()
        {
            Console.WriteLine("Round Robin Thread Scheduling using windows 32 API");
        }
    }

একই ভাবে সে লিনাক্সের জন্যে ক্লাস বানালো এভাবে (যেটা IRoundRobin কে ইমপ্লিমেন্ট করে):

    
   public class LinuxRoundRobin : IRoundRobin
    {
        public void ApplyAlgorithm()
        {
            Console.WriteLine("Round Robin Thread Scheduling using Linux Kernel API");
        }
    }

এখন ক্লায়েন্ট কোডে যে এই কোড গুলো ইউজ করলো এভাবেঃ

   public class Program
    {
        public static void Main(string[] args)
        {
            IRoundRobin roundRobin = null;
 
            roundRobin = new Win32RoundRobin();
            roundRobin.ApplyAlgorithm();
 
            roundRobin = new LinuxRoundRobin();
            roundRobin.ApplyAlgorithm();
            
            
            Console.ReadLine();
        }
    }

যদু খুশি মনে গিট এ কোড পুশ দিয়ে রেডি হলো বাসায় ফিরবে । তারপর দিন উইকেন্ড ছিলো সো তার মন এমনিতেই বেশ ফুরফুরে ।
এর মধ্যে যদুর বস তাকে বললো যে , ক্লায়েন্ট শেষ মুহূর্তে তার রিকোয়ারমেন্ট চেঞ্জ করেছে সো তোমাকে আজকে একটু এক্সট্রা আওয়ারস থাকতে হবে ।
যদুর নতুন জব সে বসকে কিছু বলতে পারলো না , সে নিরস মনে বাসায় ফোন দিয়ে জানিয়ে দিলো যে তার বাসায় ফিরতে রাত হবে ।
ক্লায়েন্টের রিকোয়ারমেন্ট হচ্ছে যে , সফটওয়্যারটিকে ম্যাকের জন্যেও সাপোর্ট দিতে হবে এবং Shortest-Job-First (SJF) Scheduling অ্যালগোরিদমও ইমপ্লিমেন্ট করে দিতে হবে।
যদু হাতে এক কাপ গরম কফি নিয়ে বাকি কাজটুকু করতে বসে গেলো । সে তার কোডের ডিজাইনে আরেক লেভেল এর ইনহেরিট্যান্স অ্যাপ্লাই করলো ।
যদু ISchedulingAlgorithm নামে একটি ইন্টারফেস বানালো এবং এর ভেতরে একটি মেথড বানালো ApplyAlgorithm নামে এভাবেঃ

   public interface ISchedulingAlgorithm
    {
        void ApplyAlgorithm();
    }

এখন ক্লায়েন্টের রিকোয়ারমেন্ট হচ্ছে RoundRobin এবং SJF Scheduling Algorithm সেহেতু আমরা দুইটা ইন্টারফেস বানাবো যারা ISchedulingAlgorithm কে ইমপ্লিমেন্ট করবে ।
আমাদের ইন্টারফেস দু’টো হচ্ছে এরকমঃ

public interface IRoundRobin : ISchedulingAlgorithm
{
}

এবং

public interface IShortestJobFirst : ISchedulingAlgorithm
{
 
}

এখন এই দুটি ইন্টারফেসকে ইউজ করে আমরা আমাদের উইন্ডোজ এবং লিনাক্সের Scheduling Algorithm ইমপ্লিমেন্ট করবো । এখানে উইন্ডোজ এবং লিনাক্সের জন্যে RoundRobin Scheduling Algorithm ইমপ্লিমেন্ট করবো এভাবেঃ
উইন্ডোজ এর জন্যেঃ

public class Win32RoundRobin : IRoundRobin
   {
       public void ApplyAlgorithm()
       {
           Console.WriteLine("Round Robin Thread Scheduling using windows 32 API");
       }
   }

লিনাক্সের জন্যেঃ

   public class LinuxRoundRobin : IRoundRobin
    {
        public void ApplyAlgorithm()
        {
            Console.WriteLine("Round Robin Thread Scheduling using Linux Kernel API");
        }
    }

একই ভাবে SJF Algorithm এর ক্ষেত্রে উইন্ডোজ জন্যেঃ

   public class Win32ShortestJobFirst : IShortestJobFirst
   {
       public void ApplyAlgorithm()
       {
           Console.WriteLine("Shortest Job First Thread Scheduling using windows 32 API");
       }
   }

লিনাক্সের জন্যেঃ

   public class LinuxShortestJobFirst : IShortestJobFirst
   {
       public void ApplyAlgorithm()
       {
           Console.WriteLine("Shortest Job First Thread Scheduling using Linux Kernel API");
       }
   }

সবশেষে ক্লায়েন্ট কোডে যদু ইউজ করলো এভাবেঃ

   public class Program
    {
        public static void Main(string[] args)
        {
            IRoundRobin roundRobin = null;
 
            roundRobin = new Win32RoundRobin();
            roundRobin.ApplyAlgorithm();
 
            roundRobin = new LinuxRoundRobin();
            roundRobin.ApplyAlgorithm();
 
            
            IShortestJobFirst shortestJobFirst = null;
 
            shortestJobFirst = new LinuxShortestJobFirst();
            shortestJobFirst.ApplyAlgorithm();
 
            shortestJobFirst = new Win32ShortestJobFirst();
            shortestJobFirst.ApplyAlgorithm();
            Console.ReadLine();
        }
    }

এ পর্যন্ত সবই ঠিক ছিলো , যখন যদু ম্যাক এর জন্যে তার অ্যালগোরিদম লিখতে লাগলো তখন সে বেশ কিছু জিনিস নিয়ে চিন্তা করতে লাগলো , সে ভাবলো অপারেটিং সিস্টেমের থ্রেড শিডিউলিং অ্যালগোরিদম অনেক গুলো আছে এবং অপারেটিং সিস্টেমও অনেকগুলো আছে (অনেক ডিভাইসও আছে যেখানে অনেক অপারেটিং সিস্টেম থাকে)।
যদু তার কোডের ক্লাস ডিজাইন এর দিকে তাকালোঃ

Bridge Design Pattern CD
Bridge Design Pattern CD

এখন যদি সে নতুন কোনও অ্যালগোদিম যোগ করতে চায় তাহলে তাকে বর্তমান দুইটি অপারেটিং সিস্টেমের জন্যে মোট ১+১+১=৩ টি ক্লাস এড করতে হবে । তাহলে মোট ক্লাস হবে ৩+৩+৩+১ = ১০ টি । প্রতিবার নতুন অ্যালগোরিদম যোগ করার সময় যদুকে তিনটি করে ক্লাস এড করতে হবে ।
আবার যদি এখানে ম্যাকের জন্যে সাপোর্ট দিতে হয় তাহলে তাকে ১টি করে ক্লাস যোগ করতে হবে । সো যদি অপারেটিং সিস্টেমের সাপোর্ট এবং অ্যালগোরিদমের সংখ্যা বাড়তে থাকে তাহলে যদুকে অনেক অনেক ক্লাস ব্যবহার করতে হবে । এভাবে আস্তে আস্তে সিস্টেমের কমপ্লিক্সিটি বাড়তে থাকবে ।
যদু এই অবস্থায় কোড পুশ করে গভীর রাতে বাসায় চলে গেলো । কিন্তু তার মাথায় কোডের ডিজাইনগুলো ঘুরপাক খেতে লাগলো । সে এই প্রবলেম কিভাবে সল্ভ করবে এই চিন্তায় ভালো করে ঘুমাতে পারলো না । পরদিন একটু দেরী করে অফিসে গিয়ে সে একজন সিনিওর ডেভেলপার এর সাথে বসে তার কোডের প্রবলেম গুলো আলোচনা করতে লাগলো । সিনিওর ডেভেলপার তার প্রবলেম শুনে তাকে একটা শব্দ বললো “ব্রিজ ডিজাইন প্যাটার্ন” । যদু তার ডেস্কে ফিরে এককাপ কফি হাতে নিয়ে নেটে সার্চ দিলো “ব্রিজ ডিজাইন প্যাটার্ন” দিয়ে । কিছুক্ষণ স্টাডি করার পর সে এটা বুঝতে পারলো যে তাকে তার কোডের মইয়ের মত ইনহেরিট্যান্স ব্রেক করতে হবে এবং একটা “ব্রীজ” এর ব্যবস্থা করতে হবে যাতে করে কোডের “অ্যালগোরিদম” এবং “অপারেটিং সিস্টেম” ডিকাপল্ড হয় ।
এইভেবে সে একটি ইন্টারফেস নিলো IScheduling নামে এবং এর ভেতরে ApplyAlgorithm() নামে একটি মেথড নিলো এভাবেঃ

  public interface ISchedulingAlgorithm
   {
       void ApplyAlgorithm();
   }

এবং যে কোনও অ্যালগোরিদম অ্যাপ্লাই করার জন্যে সে একটি ব্রিজ বানালো যাতে করে যে কোনও অপারেটিং সিস্টেম যে কোনও অ্যালগোরিদম

public abstract class AbstractOsBridge
    {
        protected ISchedulingAlgorithm SchedulingAlgorithm;
 
        protected AbstractOsBridge(ISchedulingAlgorithm schedulingAlgorithm)
        {
            SchedulingAlgorithm = schedulingAlgorithm;
        }
        public abstract void ExecuteAlgorithm();
    }

এখন যদু লিনাক্স কার্নেল এর জন্যে অ্যালগোরিদম একজিকিউট করলো এভাবেঃ

public class LinuxKernelApi : AbstractOsBridge
    {
        public LinuxKernelApi(ISchedulingAlgorithm schedulingAlgorithm)
            : base(schedulingAlgorithm)
        {
        }
        public override void ExecuteAlgorithm()
        {
            
            SchedulingAlgorithm.ApplyAlgorithm();
            Console.WriteLine("in Linux kernel...");
        }
    }

এবং উইন্ডোজ এর জন্যে অ্যালগোরিদম একজিকিউট করলো এভাবেঃ

public class Windows32Api : AbstractOsBridge
    {
        public Windows32Api(ISchedulingAlgorithm schedulingAlgorithm)
            : base(schedulingAlgorithm)
        {
        }
 
        public override void ExecuteAlgorithm()
        {
            SchedulingAlgorithm.ApplyAlgorithm();
            Console.WriteLine("in Windows 32 Api...");
        }
    }

সবশেষে যদু তার ক্লায়েন্ট কোডে একে ইউজ করলো এভাবেঃ

public class Program
    {
        public static void Main(string[] args)
        {
            AbstractOsBridge abstractOsBridge = null;
            abstractOsBridge = new LinuxKernelApi(new RoundRobinAlgorithm());
            abstractOsBridge.ExecuteAlgorithm();

            abstractOsBridge = new Windows32Api(new ShortestJobFirst());
            abstractOsBridge.ExecuteAlgorithm();

            Console.ReadLine();
        }
    }

এখন যদু অনায়াসেই যে কোনও অ্যালগোরিদম যে কোনও অপারেটিং সিস্টেমের জন্যে ইমপ্লিমেন্ট করতে পারবে কোনও রকম অবজেক্ট অরিয়েন্টেড প্রিন্সিপাল এর ভায়োলেশন ছাড়াই এবং অনেক অনেক ক্লাস বানানো ছাড়াই ।
যেমন যদুর বস যদি যদুকে নতুন অ্যালগোরিদম যোগ করতে বলে সে একটি ক্লাস বানাবে যে ক্লাসটা ISchedulingAlgorithm

কে ইমপ্লিমেন্ট করবে । এরপর ক্লায়েন্ট কোডে যে কোনও অপারেটিং সিস্টেমের অবজেক্টের ভেতর দিয়ে অ্যালগোরিদম ক্লাসটির অবজেক্ট বানিয়ে পাঠিয়ে দিলেই যদুর অ্যালগোরিদম কাজ করবে । আবার নতুন অপারেটিং সিস্টেম যোগ করতে চাইলে নতুন ক্লাস বানিয়ে সেটা AbstractOsBridge কে এক্সটেন্ড করলেই হয়ে যাচ্ছে । এভাবে ব্রিজ ডিজাইন প্যাটার্ন দিয়ে অপারেটিং সিস্টেম এবং থ্রেড অ্যালগোরিদম এর মধ্যেকার টাইটলি কাপল্ড ডিপেন্ডেন্সিকে লুজলি কাপল্ড বানানো যায়।

এই ছিলো আমাদের ব্রীজ ডিজাইন প্যাটার্ন।
হ্যাপি কোডিং 😀

মেমেন্টো ডিজাইন প্যাটার্ন

মেমেন্টো ডিজাইন প্যাটার্ন লিখার আগে মেমেন্টো মুভিটার কথা না বললেই নয়। কিংবদন্তী পরিচালক ক্রীস্টোফার নোলান তার মেমেন্টো মুভিতে হিরো হিসেবে এমন একটি ক্যারেকটার দেখান যে অ্যামনেশিয়াতে আক্রান্ত । হিরো তার লাইফের ইভেন্টগুলো ব্যাক ট্রেস করে খুনিকে ধরার চেষ্ঠা করে।

memento

(ছবিটি অনলাইন থেকে মেরে দেয়া :P)

তো মেমেন্টো মুভি আর মেমেন্টো ডিজাইন প্যাটার্ন অনেকটা একই রকম ।

মেমেন্টো ডিজাই প্যাটার্নঃ মেমেন্টো ডিজাইন প্যাটার্ন হচ্ছে এমন একটি এপ্রোচ যেটা দিয়ে অবজেক্ট এর ইন্টার্নাল স্টেট একটি এক্সটার্নাল জায়গায় সেভ করা যায় এবং সময়মত দরকার হলে সেটা রিস্টোর করা যায়

এখন প্রশ্ন আসতে পারে , অবজেক্ট এর ইন্টার্নাল স্টেট যদি এক্সটার্নাল একটি জায়গায় রেখে দিই তাহলে এটি অবজেক্ট ওরিয়েন্টেড ডিজাইন প্রিন্সিপাল এর এনক্যাপশুলেশন কে ভায়োলেট করবে কিনা ? উত্তর হচ্ছে “না”

গফের সংজ্ঞা আনুযায়ী “Without violating encapsulation, capture and externalize an object’s internal state so that the object can be restored to this state later.

এখন আমরা দেখে নেবো, কিভাবে মেমেন্টো ডিজাইন প্যাটার্ন কোডে অ্যাপ্লাই করতে পারি।এর আগে জেনে নেয়া ভালো যে,মেমেন্টো ডিজাইন প্যাটার্ন তিনটি অবজেক্ট নিয়ে কাজ করে এগুলো হচ্ছেঃ

  1. Originator: মুল অবজেক্ট যার স্টেট সেভ করতে হবে এবং রীস্টোর করতে হবে ।
  2. Memento: Memento কে Originator ইউজ করে Originator এর স্টেট সেভ করে রাখে।
  3. Caretaker: Memento এর রিপোজিটোরি হিসেবে কাজ করে(Create,Update,Delete) কিন্তু Memento এর কন্টেন্ট উপর সরাসরি কখনই কাজ করে না।

আমরা প্রথমে মেমেন্টো ক্লাস বানাবো এভাবেঃ

   public class Memento
    {
        public Memento(string state)
        {
            State = state;
        }

        public string State { get; private set; }
    }

উদাহরণের সুবিধার জন্যে class এ আমরা শুধুমাত্র একটি প্রোপার্টি “state” রেখেছি।
এখন Memento এর রিপোজিটরী CareTaker class বানিয়ে ফেলি এভাবেঃ

    public class Caretaker
    {
        private readonly List<Memento> _savedStateList = new List<Memento>();
 
        public void AddMemento(Memento memento)
        {
            _savedStateList.Add(memento);
        }
 
        public Memento GetMemento(int index)
        {
            return _savedStateList[index];
        }
    }

উদাহরণের সুবিধা/সিমপ্লিসিটির জন্যে আমরা এখানে Memento এর একটি লিস্ট নিয়েছি এবং এই লিস্টে Memento Add, এবং Get করার ফাংশনালিটি দেয়া হয়েছে ।

এখন চলুন আমরা মেমেন্টো ডিজাইন প্যাটার্নের মূল অবজেক্ট “Originator” বানিয়ে ফেলি । এই অবজেক্ট এর ষ্টেট গুলোই মুলত আমাদের সেভ এবং রিস্টোর করতে হবে।

 
  public class Originator
   {
       private string _state;
       public string State
       {
           private get
           {
               return _state;
 
           }
           set
           {
               _state = value;
               Console.WriteLine("Originator: Setting state to " + _state);
           }
       }
       public Memento SaveToMemento()
       {
           Console.WriteLine("Originator: Saving to Memento.");
           return (new Memento(_state));
       }
       public void RestoreFromMemento(Memento memento)
       {
           State = memento.State;
           Console.WriteLine("Originator: State after restoring from Memento: " + State);
 
       }
   }

এখানে আমরা তিনটি কাজ করেছি

  1. অবজেক্ট এর ষ্টেট সেট করার সময় ষ্টেট এর নাম প্রিন্ট করেছি
  2. অবজেক্ট এর ষ্টেট Memento এ সেভ করে ঐ Memento রিটার্ন করেছি।
  3. অবজেক্ট এর ষ্টেট Memento থেকে রিস্টোর করেছি ।

এখন দেখা যাক আমরা আমাদের ক্লায়েন্ট কোড এ মেমেন্টো ডিজাইন প্যাটার্ন কিভাবে ইউজ করিঃ

public class Program
{
     public static void Main(string[] args)
     {
          Caretaker careTaker = new Caretaker();
          Originator originator = new Originator();
            
          originator.State = "State1";
          careTaker.AddMemento(originator.SaveToMemento());
            
          originator.State = "State2";
          careTaker.AddMemento(originator.SaveToMemento());
 
          originator.State = "State3";
          careTaker.AddMemento(originator.SaveToMemento());
            
            
          originator.State = "State4";
          careTaker.AddMemento(originator.SaveToMemento());
 
          originator.RestoreFromMemento(careTaker.GetMemento(2));
            
          Console.ReadLine();
        }
    }

এখানে আমরা CareTaker এবং Originator আমরা এর অবজেক্ট বানিয়েছি ।
এরপর Originator এর ষ্টেট এসাইন করেছি, Caretaker কে দায়িত্ব দিয়েছি Originator এর সবগুলো ষ্টেট সেভ করে রাখতে এবং সবশেষে Originator থেকে (কোডে তিন নাম্বার) ষ্টেট রিস্টোর করেছি । সো এই ছিলো আমাদের Memento ডিজাইন প্যাটার্ন 🙂
হ্যাপি কোডিং 🙂

মিডিয়েটর ডিজাইন প্যাটার্ন

অবজেক্ট ওরিয়েন্টেড প্রোগ্রামিং এ টাইটলি কাপলড নামে একটা বিষয় আছে ।বিষয়টা এমন যে যখন দুটো/এর বেশী অব্জেক্ট/মডিউল একে অপরের সাথে সরাসরি ডেটা আদান প্রদান করে , অথবা কোনও একটি অবজেক্ট এর সামান্য কিছু চেঞ্জ হলে সাথে সাথে অন্যান্য অবজেক্ট এরও কিছু ডেটা/একশন চেঞ্জ হয়ে যায়। টাইটলি কাপলড সিস্টেমে এক বা একাধিক অব্জেকট অন্যান্য অবজেক্ট এর সাথে তৃতীয় কোনও মাধ্যম ছাড়াই সরাসরি যোগাযোগ / ডেটা আদান প্রদান করে থাকে । বড় বড় এন্টারপ্রাইজ অ্যাপ্লিকেশনের কথাই ধরুন যেখানে শত শত অবজেক্ট নিয়ে কাজ হয় সেখানে যদি এই অবজেক্ট গুলো একে অপরের সাথে এরকম টাইটলি কাপলড অবস্থায় থাকে তাহলে এই অ্যাপ্লিকেশন ডেভেলপ করা , মেইনটেইন করা , খুব সহজেই ফাংশনালিটি চেঞ্জ করা যে কোনও ডেভেলপারের দুঃস্বপ্নের কারণ হিসেবে দেখা দিবে। নিচের ছবিটি দেখে ধারণা নিতে পারেন কি অবস্থা হতে পারেঃ
mediator-design-pattern

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

মিডিয়েটর ডিজাইন প্যাটার্ন ব্যবহারের আগেঃ

ধরুন আমরা একটি মেসেঞ্জার বানাতে চাই, যেখানে অনেকগুলো ইউজার থাকবে । ইউজার  অন্যান্য ইউজার কে মেসেজ পাঠাতে পারবে অথবা কোনও ফাইল এটাচমেন্ট হিসেবে পাঠাতে পারবে।

ইউজার এর জন্য আমাদের একটি ক্লাস লাগবে । এখন আসুন দেখে নিই এই ইউজার ক্লাসে কি কি থাকবেঃ

  • ইউজারের নাম ।
  • ইউজারের ম্যাসেজ ।
  • যাদের পাঠাবে তাদের একটি লিস্ট, অর্থাৎ ইউজারের লিস্ট ।
  • ম্যাসেজ সেন্ড করার জন্য একটি মেথড , যেখানে আর্গুমেন্ট হিসেবে একটি ইউজারের অবজেক্ট থাকবে (কেননা একজন ইউজার অন্য ইউজার কে ম্যাসেজ পাঠাবে)
  • ইউজার চেক করার জন্য একটি মেথড (চেক করা লাগবে কেননা একজন ইউজার একজন ইউজারকে ম্যাসেজও পাঠাতে পারে আবার হয়তো অন্য কোনও ইউজারকে একটা এটাচমেন্ট হিসেবে একটা ফাইলও পাঠাতে পারে)

তাহলে আমাদের ইউজার ক্লাসটা এরকম হবেঃ


public abstract class User
{
       protected string Name { get; set; }
       public string Message { get; set; }
       protected readonly List<User> PersonList = new List<User>();
       protected User(string name)
       {
           Name = name;
       }

       public void SendMessage(User user)
       {
           if (user != null)
           {
               PersonList.Add(user);
           }

           UserChecking();
       }
       protected abstract void UserChecking();
   }

ক্লাস এবস্ট্রাক্ট রাখা হয়েছে কেননা আলাদা আলাদা ইউজারকে আলাদা আলাদা ম্যাসেজ / ফাইল পাঠানো হতে পারে ।

এখন আমরা দুইটা ইউজার বানাবো যারা এই ইউজার ক্লাসকে এক্সটেন্ড করবে।

প্রথম ইউজারঃ

     public class UserOne : User
     {
        public UserOne(string name)
            : base(name)
        {
        }

        protected override void UserChecking()
        {
            foreach (User person in PersonList)
            {
                if (person.GetType() == typeof(UserTwo))//User will send message to UserTwo Only
                {
                    Console.WriteLine("From " + Name + ":" + Message);
                }

            }
        }
    }

এখানে লুপের ভেতর চেক করা হয়েছে যে প্রথম ইউজার শুধু মাত্র অন্য ইউজারের কাছে ম্যাসেজ পাঠাতে পারবে ( আমাদের ক্ষেত্রে ইউজার টু)।

একই ভাবে দ্বিতীয় ইউজারঃ

    public class UserTwo : User
    {
        public UserTwo(string name)
            : base(name)
        {
        }

        protected override void UserChecking()
        {
            foreach (User person in PersonList)
            {
                if (person.GetType() == typeof(UserOne))
                {
                    Console.WriteLine("From " + Name + ":" + Message);
                }
            }
        }
    }

দ্বিতীয় ইউজারের ক্ষেত্রে চেক করা হয়েছে যে এটি শুধু প্রথম ইউজারের কাছে মেসেজ পাঠাতে পারবে ।
এখন এই কোড আমাদের ক্লায়েন্ট কোডে ইউজ করতে পারি এভাবেঃ

    public class Program
    {
        public static void Main(string[] args)
        {
            var personOne = new UserOne("Moin")
            {
                Message = "Hi Sujon,  How are you?"
            };

            var personTwo = new UserTwo("Sujon")
            {
                Message = "I am fine, but where is my money?"
            };
            personOne.SendMessage(personTwo);
            personTwo.SendMessage(personOne);
            Console.ReadLine();
        }
    }

আমরা যে চ্যাট সিস্টেম ডেভেলপ করলাম এর সমস্যাগুলো আসুন চিহ্নিত করিঃ

  1. প্রত্যেক ইউজার প্রত্যেক ইউজারের সাথে সরাসরি যোগাযোগ করছে ।
  2. এই চ্যাট অ্যাপ্লিকেশন ১০০ জন ইউজ করলে আমাদের ১০০ বার If else কন্ডিশন লাগাতে হবে যেটা  OOP এর open closed principle রুল এর ভায়োলেশন । কেননা নতুন ইউজার আসলে আমাদের নতুন কন্ডিশন যোগ করতে হবে ।
  3. ১০০ জন ইউজার এই সিস্টেম ইউজ করলে সবার কাছে ১০০*৯৯ টি If Else কন্ডিশন থাকবে !!!!!! চিন্তা করা যায় ।
  4. এই অবজেক্ট গুলোর কোনও একটিতে সামান্য একটু চেঞ্জ হলে , বাকি সবগুলো অবজেক্ট গুলোকে নোটিফাই করতে হবে , যেটা ব্যাপক পিড়াদায়ক কাজ ।

মিডিয়েটর ডিজাইন প্যাটার্ন ইউজ করার পরঃ

মিডিয়েটর ডিজাইন প্যাটার্ন আমাদের অবজেক্টদের মধ্যে যোগাযোগের মাধ্যম হিসেবে কাজ করে । অর্থাৎ এই প্যাটার্ন আমাদের অবজেক্টদের মুরুব্বী হিসেবে কাজ করে এবং খেয়াল রাখে যাতে করে এরা নিজেদের মধ্যে সরাসরি যোগাযোগ করতে না পারে । কোনোও অবজেক্ট অন্য অবজেক্ট এর সাথে যোগাযোগ করতে হলে অবশ্যই এই মুরুব্বীর মাধ্যমে করতে হবে । একটি অবজেক্ট এই মুরুব্বীকে বলবে যে আমার অন্য একটি অবজেক্ট এর কাছে কিছু বার্তা পাঠানো দরকার , মুরুব্বী সেই বার্তা নিয়ে কাংখিত অবজেক্ট এর কাছে বার্তা পাঠিয়ে দিবে ।

তাহলে আমাদের মুরুব্বীর প্রধান কাজ হচ্ছে ইউজারের কাছ থেকে ম্যাসেজ নেয়া এবং কাংখিত ইউজারকে ম্যাসেজ সেন্ড করা ।
সো এই দুইটি কাজের জন্য আমরা একটা ইন্টারফেস বানাতে পারি এভাবেঃ

    public interface IChatMediator
    {
        void SendMessage(string message, User user);
        void AddPerson(User user);
     }

এখন এই মুরুব্বীকে আমাদের কোড এ ইউজ করতে গেলে আমাদের ইউজার ক্লাসে একটু চেঞ্জ করতে হবে এভাবেঃ

  public abstract class User
   {

       protected IChatMediator ChatMediator;
       protected string Name { get; set; }

       protected User(string name, IChatMediator chatMediator)
       {
           Name = name;
           ChatMediator = chatMediator;
       }

       public abstract void Send(string message);
       public abstract void Receive(string message);
   }

এবং আমাদের মুরুব্বী মিডিয়েটর ইন্টারফেসের ইমপ্লিমেন্টেশন হবে এভাবেঃ

   public class ChatMediator : IChatMediator
    {
        private readonly List<User> _personList;

        public ChatMediator()
        {
            _personList = new List<User>();
        }
        public void SendMessage(string message, User user)
        {

            foreach (User u in _personList)
            {
                //message should not be received by the user sending it
                if (u != user)
                {
                    u.Receive(message);
                }
            }
        }

        public void AddPerson(User user)
        {
            _personList.Add(user);

        }
    }

এখন ইউজার ক্লাসের ইমপ্লিমেন্টেশন হবে এরকমঃ

    public class UserOne : User
    {
        public UserOne(string name, IChatMediator chatMediator)
            : base(name, chatMediator)
        {
        }

        public override void Send(string message)
        {
            Console.WriteLine(Name + " :Sending Message=" + message);
            ChatMediator.SendMessage(message, this);
        }

        public override void Receive(string message)
        {
            Console.WriteLine(Name + " :Received Message:" + message);
        }
    }

ব্যস আমাদের মুরুব্বী রেডি , এখন আমরা অনেকগুলো ইউজার বানাবো আর তাদের কে হ্যালো বলবো এভাবেঃ

   public class Program
    {
        public static void Main(string[] args)
        {
            IChatMediator chatMediator = new ChatMediator();
            User me = new UserOne("Forhad", chatMediator);
            User person1 = new UserOne("Shuvo", chatMediator);
            User person2 = new UserOne("Sujon", chatMediator);
            User person3 = new UserOne("Moin", chatMediator);
            chatMediator.AddPerson(person1);
            chatMediator.AddPerson(person2);
            chatMediator.AddPerson(person3);

            me.Send("Hi All, How are you?");

            Console.ReadLine();
        }
    }

লক্ষ্য করে দেখুন , এখানে কোনও অবজেক্ট সরাসরি ইউজার অবজেক্ট এর কাছে ম্যাসেজ পাঠাচ্ছে না , পাঠাচ্ছে আমাদের মুরুব্বীর কাছে ।
এভাবে আমরা মিডিয়েটর ডিজাইন প্যাটার্ন দিয়ে অনেক স্ক্যালাবল এবং লুজলি কাপলড সিস্টেম বানাতে পারি ।

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

ভিজিটর ডিজাইন প্যাটার্ন

মনে করুন এমন একটি সফটওয়্যার ডেভেলপ করতে চাই যেটা দিয়ে বাংলাদেশের সব ধরনের অপারেটরের মডেম গুলো দিয়ে ইন্টারনেট এক্সেস করা যাবে । এখন বর্তমানে টেলিটক , জুম(সিটিসেল) এবং গ্রামীণ এই তিনটি অপারেটরের মডেম  চালু রয়েছে । Simplicity এর জন্য আমরা ধরে নিই যে , সফটওয়্যারটির শুধুমাত্র দুইটি বাটন রয়েছে , একটি হচ্ছে মডেম কানেক্ট করার জন্য আরেকটি হচ্ছে ডিসকানেক্ট করার জন্য । তাহলে এই তিন প্রকার মডেমের কমন বৈশিষ্ট গুলো হচ্ছে

  • কানেক্ট করা
  • ডিসকানেক্ট করা

আমরা জানি যে, অনেকগুলো অবজেক্ট এর যে সব কমন প্রপার্টি / কাজ আছে সেগুলোকে একটি কমন ইন্টারফেস/এবস্ট্রাক্ট class এ নিয়ে যাওয়া উচিত । সো মনে করুন কমন ইন্টারফেসটির নাম হচ্ছে IModem। তাহলে আমদের ইন্টারফেসটি দাঁড়াল এরকমঃ

public interfaceIModem
{
     void Connect();
     void Disconnect();
}

এখন তিন প্রকার মডেমের জন্য আমাদের তিনটি class যথাক্রমে Grameen, Teletalk এবং Zoom যারা প্রত্যকেই IModem ইন্টারফেসটিকে ইমপ্লিমেন্ট করবে । তাহলে আমরা class তিনটি লিখে ফেলি।

গ্রামীণ মডেমঃ

    public class Grameen : IModem
    {
        public void Connect()
        {
            Console.WriteLine("Grameen Modem Successfully connected");
        }
        public void Disconnect()
        {
            Console.WriteLine("Grameen Modem Successfully disconnected");
        }
    }

জুম মডেমঃ

    public class Zoom : IModem
    {
        public void Connect()
        {
            Console.WriteLine("Zoom Modem Successfully connected");
        }

        public void Disconnect()
        {
            Console.WriteLine("Zoom Modem Successfully disconnected");
        }
     }

টেলিটক মডেমঃ

    public class Teletalk : IModem
    {
        public void Connect()
        {
            Console.WriteLine("Teletalk Modem Successfully connected");
        }
        public void Disconnect()
        {
            Console.WriteLine("Teletalk Modem Successfully disconnected");
        }
    }

এখন মনে করুন ConfigureForUnix() নামে আমাদের নতুন একটি মেথড এর দরকার পড়ছে যেটা দিয়ে আমাদের এই মডেম গুলো Unix অপারেটিং এ কাজ করবে ।

ConfigureForUnix() মেথডের মধ্যে আলাদা আলাদা মডেম তাদের নিজস্ব ইমপ্লিমেন্টেশন লিখে রাখবে যাতে করে সবগুলো মডেম Unix অপারেটিং সিস্টেমে চলে।

তাহলে আমরা ConfigureForUnix() মেথডটি IModem ইন্টারফেসে নিয়ে যেতে পারি । তাহলে সবকয়টি মডেম ConfigureForUnix() ইউজ করে Unix অপারেটিং সিস্টেমে চলবে।

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

   public interface IModem
    {
        void Connect();
        void Disconnect();
        void ConfigureForUnix();
    }

এবং আমাদের বাকি মডেম class গুলো এরকম দাঁড়াবেঃ

গ্রামীণ মডেমঃ

    public class Grameen : IModem
    {
        public void Connect()
        {
            Console.WriteLine("Grameen Modem Successfully connected");
        }
        public void Disconnect()
        {
            Console.WriteLine("Grameen Modem Successfully disconnected");
        }
        public void ConfigureForUnix()
        {
            Console.WriteLine("Configure Grameen Modem For Unix Operating System");
        }
    }

জুম মডেমঃ

    public class Zoom : IModem
    {
        public void Connect()
        {
            Console.WriteLine("Zoom Modem Successfully connected");
        }
        public void Disconnect()
        {
            Console.WriteLine("Zoom Modem Successfully disconnected");
        }
        public void ConfigureForUnix()
        {
            Console.WriteLine("Configure Zoom Modem For Unix Operating System");
        }
    }

এবং টেলিটক মডেমঃ

    public class Teletalk : IModem
    {
        public void Connect()
        {
            Console.WriteLine("Teletalk Modem Successfully connected");
        }
        public void Disconnect()
        {
            Console.WriteLine("Teletalk Modem Successfully disconnected");
        }
        public void ConfigureForUnix()
        {
            Console.WriteLine("Configure Teletalk Modem For Unix Operating System");
        }
    }

এখন ধরুন আমাদের ম্যাক অপারেটিং সিস্টেমের জন্য মডেম এর কনফিগারেশন লিখতে হবে । তাহলে আমাদের ইন্টারফেস এ ম্যাক কনফিগার করার জন্য আরোও একটি নতুন মেথড অ্যাড করতে হবে । এভাবে যতগুলো অপারেটিং সিস্টেম / ডিভাইস এর জন্য আমাদের মডেমগুলো করফিগার করতে হবে ততগুলো মেথড আমাদের ইন্টারফেসে অ্যাড করতে হবে।

তাহলে এভাবে চলতে থাকলে আমরা আমাদের IModem ইন্টারফেসটিকে কখনই ক্লোজ করতে পারবো না অর্থাৎ আমরা যখনই নতুন মেথড অ্যাড করতে যাব তখনই ইন্টারফেস এ মোডিফিকেকশন করতে হবে যা কিনা Open Close Principle (OCP) এর ভায়োলেশন ।

তাহলে আমরা এই সমস্যার সমাধান করতে পারি কিভাবে ? আমরা এই সমস্যার সমাধান করতে পারি ভিজিটর ডিজাইন প্যাটার্ন দিয়ে । ভিজিটর ডিজাইন প্যাটার্ন একটি টেকনিক ইউজ করে যেটার নাম হচ্ছে Dual Dispatch. এর সম্পর্কে হালকা ধারণার জন্যে পোষ্টের শেষে দেখুন

তাহলে চলুন Dual Dispatch টেকনিক ইউজ করে আমরা এই সমস্যাটার সমাধান করে ফেলি।

প্রথমে আমরা একটি ইন্টারফেস বানাবো যার নাম হচ্ছে IModemVisitor

আমাদের নতুন বানানো ইন্টারফেসটি দেখতে এরকমঃ

   public interface IModemVisitor
    {
        void Visit(Grameen grammenModem);
        void Visit(Teletalk teleTalkModem);
        void Visit(Zoom zoomModem);
    }

এখন আমাদের আগের IModem ইন্টারফেসটি সামান্য চেঞ্জ করে এরকম করা হলো যাতে করে এটি IModemVisitor ইন্টারফেসকে ব্যবহার করতে পারে ।

    public interface IModem
    {
        void Connect();
        void Disconnect();
        void Accept(IModemVisitor modemVisitor);
    }

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

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

গ্রামীণ মডেমঃ কনফিগার ফর উইন্ডোজঃ

   public class Grameen : IModem
    {
        public void Connect()
        {
            Console.WriteLine("Grameen Modem Successfully connected");
        }

        public void Disconnect()
        {
            Console.WriteLine("Grameen Modem Successfully disconnected");
        }
        public void Accept(IModemVisitor modemVisitor)//Dual Dispatch
        {
            modemVisitor.Visit(this);
        }
        public void ConfigurationForWindows()
        {
            Console.WriteLine("Configuring Grameen modem for Windows Operating System");
        }

    }

জুম মডেম কনফিগার ফর ম্যাকঃ

  public class Zoom : IModem
    {
        public void Connect()
        {
            Console.WriteLine("Zoom Modem Successfully connected");
        }
        public void Disconnect()
        {
            Console.WriteLine("Zoom Modem Successfully disconnected");
        }
        public void Accept(IModemVisitor modemVisitor)
        {
            modemVisitor.Visit(this);
        }
        public void ConfigurationForMac()
        {
            Console.WriteLine("Configuring zoom modem for Mac Operating System");
        }
    }

এবং টেলিটক মডেম কনফিগার ফর ইউনিক্সঃ

    public class Teletalk : IModem
    {
        public void Connect()
        {
            Console.WriteLine("Teletalk Modem Successfully connected");
        }
        public void Disconnect()
        {
            Console.WriteLine("Teletalk Modem Successfully disconnected");
        }
        public void Accept(IModemVisitor modemVisitor)
        {
            modemVisitor.Visit(this);
        }
        public void ConfigurationForUnix()
        {
            Console.WriteLine("Configuring Teletalk modem for Unix Operating System");
        }

    }

All Operating System এর জন্য আমরা নতুন একটি class বানাবো যেটি আমাদের নতুন ইন্টারফেস IModemVisitor কে ইমপ্লিমেন্ট করবে এভাবেঃ

    public class AllModemConfigurator : IModemVisitor
    {
        public void Visit(Grameen grammenModem)
        {
            grammenModem.Connect();
            grammenModem.ConfigurationForWindows();
            grammenModem.Disconnect();
        }
        public void Visit(Teletalk teleTalkModem)
        {
            teleTalkModem.Connect();
            teleTalkModem.ConfigurationForUnix();
            teleTalkModem.Disconnect();
        }
        public void Visit(Zoom zoomModem)
        {
            zoomModem.Connect();
            zoomModem.ConfigurationForWMac();
            zoomModem.Disconnect();
        }
    }

তাহলে আমরা আমাদের ক্লায়েন্ট কোড এ মডেম ভিজিটরকে ইউজ করতে পারি এভাবেঃ

        public static void Main(string[] args)
        {
            IModemVisitor modemVisitor = new AllModemConfigurator();

            var modemList = new List<IModem>
            {
                new Grameen(),
                new Teletalk(),
                new Zoom()
            
            };
            foreach (var modem in modemList)
            {
                modem.Accept(modemVisitor);
            }
            Console.ReadLine();
        }

এখন নতুন কোনও অপারেটিং সিস্টেমের জন্য (যেমন এন্ড্রোয়েড)এবং নতুন কোনও মডেমের জন্য (যেমন এয়ারটেল) কনফিগারেশন লিখতে হলে আমাদের নিম্নলিখিত কাজ গুলো করতে হবেঃ

১।নতুন মডেমের class বানাতে হবে যেটি IModem ইন্টারফেসকে ইমপ্লিমেন্ট করবে

২)নতুন মডেমের class এর মধ্যে নতুন একটি মেথড বানিয়ে এর মধ্যে এন্ড্র্যেয়েডের জন্য কনফিগারেশন লিখতে হবে ।

৩)IModemVisitor ইন্টারফেসের Visit() মেথডের মধ্যে  আমাদের নতুন মডেমের অবজেক্ট এর রেফারেন্স পাস করে দিতে হবে ।

সবশেষে IModemVisitor দিয়ে একে কল করতে হবে।

তাহলে দেখা যাচ্ছে আমাদের ভিজিটর ডিজাইন প্যাটার্ন ব্যবহার করলে IModem ইন্টারফেসে কোনও রকম চেঞ্জ না করেই আমরা যে কোনও Concrete Class এ নতুন নতুন ফিচার অ্যাড করতে পারি । এটাই হচ্ছে ভিজিটর ডিজাইন প্যাটার্নের বৈশিষ্ট্য ।

Double Dispatch কিঃ

তো ডুয়েল ডিসপ্যাচ টা আসলে কি?  ডুয়েল ডিসপ্যাচে একটি অবজেক্ট (এখানে আমাদের ক্ষেত্রে Zoom,Teletalk,Grameen)Accept()  মেথডের মাধ্যমে (আমাদের এখানে Accept() মেথড ইন্টারফেস IModem এ ডিক্লেয়ার করা হয়েছে) একটি Visitor অবজেক্ট রিসিভ করে এবং Visitor অবজেক্ট এর Visit() মেথড কে কল করে ।(সকল Visitor অবজেক্ট যেমনAllModemConfigurator,IModemVisitor ইন্টারফেসকে ইমপ্লিমেন্ট করে)।

যেহেতু Visitor অবজেক্ট এর অনেকগুলো Visit মেথড আছে সেহেতু এই মেথডের ভেতর দিয়ে যে অবজেক্ট এর রেফারেন্স পাস করা হবে সে অবজেক্ট এর ফাংশনালিটি আমরা কল করতে পারবো ।

এখানে আমাদের দুইটি কল আছে (ডাবল ডিসপ্যাচ) যেটা সঠিক অবজেক্ট সিলেক্ট করে এবং সেই সঠিক অবজেক্ট এর জন্য এর সঠিক অপারেশনগুলো কল করে  । যেমন আমরা গ্রামীণ মডেমের জন্যে Unix Operating System এর কনফিগারেশন লিখেছি । সো Double Dispatch দিয়ে আমরা গ্রামীণ মডেম সিলেক্ট করেছি এবং এর জন্যে নির্দিষ্ট করে রাখা ফাংশনগুলো কল করতে পেরেছি । এই হচ্ছে মোটামুটিভাবে Double Dispatch ম্যাকানিজম ।

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

ষ্টেট ডিজাইন প্যাটার্ন

যারা কম্পিউটার বিজ্ঞান নিয়ে পড়াশোনা করেন অথবা এর সম্পর্কে মোটামুটি খোজখবর রাখেন তারা স্যার অ্যালান টুরিং এর নাম অবশ্যই শুনে থাকবেন ।

তিনি ছিলেন একাধারে গণিতবিদ , যুক্তিবিদ এবং ক্রিপ্টোবিশেষজ্ঞ । কম্পিউটার বিজ্ঞানে তার অসামান্য অবদান হচ্ছে টুরিং মেশিন । তিনি ১৯৩৬ সালে একটা হাইপোথেটিকাল মেশিন ডিজাইন করেন যেটা অ্যালগরীদম সিমুলেট করতে পারে ।

যে অ্যালগরীদম গুলো কম্পিউটারে কম্পিউটেবল, তার এই মেশিন দ্বারা দুনিয়ার সেই অ্যালগরীদমগুলো ব্যাখ্যা করা যায় ।আধুনিক কম্পিউটারগুলো টুরিং মেশিন কম্পিউটেবল। টুরিং মেশিন দিয়ে সলভ করা না গেলে কম্পিউটার দিয়ে সলভ করা যায়না ।

ছবিঃ অ্যালান টুরিং

টুরিং মেশিন যেভাবে কাজ করেঃ

টুরিং মেশিনে অনেকগুলো স্টেট থাকে। প্রতিটা স্টেট থেকে কোন স্টেট যাওয়া যায় সেটা নির্ভর করে ওই প্রবলেমের “ট্রানজিশন ডায়াগ্রাম” এর উপর। সহজ কথায় ডায়াগ্রামে বলা থাকে কোন ইনপুটের জন্য কোন স্টেট থেকে কোন স্টেটে যেতে হবে সেটা। কম্পিউটার বিজ্ঞানের শিক্ষার্থীরা “থিওরি অফ কম্পিউটিং” অথবা “কম্পাইলার ডিজাইন” এ অটোমাটা (Automata) বিষয়টি পড়ে থাকলে বুঝতে সুবিধা হবে ।                                                 

নিচের ছবিটি বিষয়টি বুঝতে সাহায্য করবে আশাকরিঃ


চিত্রঃ ষ্টেট ট্রানজিশন ডায়াগ্রাম

অনেক কথা বলে ফেললাম । এখন আসল কথায় আসা যাক । আমরা যে টুরিং মেশিনের কথা বললাম , এই মেশিনের আচরন (মানে এটি যেভাবে কাজ করে ) কিন্তু খুব ভালোভাবে ষ্টেট ডিজাইন প্যাটার্ন দিয়ে করে ফেলা যায় ।

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

এদের চেহারা একই হলেও এরা আলাদা ভাবে সম্পূর্ণ ভিন্ন দুটি সফটওয়্যার ডিজাইন প্রবলেম সল্ভ করে।তাহলে চলুন আমরা অনেক সহজ একটি উদাহরণের সাহায্যে স্টেট ডিজাইন প্যাটার্ন ইমপ্লিমেন্ট করে ফেলি।

উদাহরণঃ

সারাদিন কাজকর্ম শেষে আমরা সবাই যে যার বাসা/মেস/হোস্টেল এ ফিরি । ফিরেই আমরা যে জিনিসটি সবার আগে দেখি সেটা হচ্ছে বাসার গেট 😀 । আমরা কখনও গেট খোলা অবস্থায় দেখি , কখনও বন্ধ অবস্থায় দেখি, কখনোবা তালা বদ্ধ অবস্থায় দেখি ।

তাহলে আমরা গেট/দরোজা’র তিনটি স্টেট/অবস্থা দেখতে পাই । এই স্টেটগুলো আমরা টুকে নিই

  1. Open
  2. Closed
  3. Locked

এখন দারোয়ান মামারা এই দরজা/গেট এর উপর সারাদিন/সারারাত কি নির্যাতন চালায় মানে এগুলোর উপর কি কাজ/একশন করে তা টুকে নিইঃ

  1. UnlockGate
  2. LockGate
  3. OpenGate
  4. CloseGate

আমরা গেট এর স্টেট/অবস্থা এবং একশন গুলো পেয়ে গেছি , এখন আমরা ঝটপট কোডে চলে যাই ।আমরা Gate নামে একটি Class বানাবো এবং এর স্টেটগুলো ভেরিয়েবল হিসেবে রেখে দিব।

সকালে উঠে অফিসে যাওয়ার সময় যেহেতু আমরা বাসার গেট খোলা দেখি এজন্য CurrentState ভ্যারিয়েবলে আমরা OpenState টি রেখে দিই । এই CurrentState আমাদের সব সময় Gate এর কারেন্ট অবস্থা সম্পর্কে জানাবে ।

সো আমাদের Class টী দেখতে এরকমঃ

    public class Gate
    {
        private const int Opened = 1;
        private const int Closed = 2;
        private const int Locked = 3;
        private const int Unlocked = 4;
        public int CurrentState = Opened;
    }

আমরা এর আগে যে একশন গুলো পেয়েছিলাম সেগুলো আমাদের এই Class এ মেথড হিসেবে ব্যবহার করবো এভাবেঃ

মেথড ১: OpenGate()

       public void OpenGate()
        {
            if (CurrentState == Opened)
            {
                Console.WriteLine("The gate is already open....");
            }
            else if (CurrentState == Closed)
            {
                CurrentState = Opened;
                Console.WriteLine("The gate state is now opened from closed state");
            }
            else if (CurrentState == Locked)
            {
                Console.WriteLine("Please unlock the gate and open it....");
            }
            else if (CurrentState == Unlocked)
            {
                CurrentState = Opened;
                Console.WriteLine("The gate is now unlocked and opened ");
            }
        }

মেথড ২: CloseGate()

        public void CloseGate()
        {
            if (CurrentState == Opened)
            {
                CurrentState = Closed;
                Console.WriteLine("The gate has been closed successfully........");

            }
            else if (CurrentState == Closed)
            {
                Console.WriteLine("The gate is already closed.You can not close it again!!");
            }
            else if (CurrentState == Locked)
            {
                Console.WriteLine("The gate is locked. So you can not close this gate!!");
            }
            else if (CurrentState == Unlocked)
            {
                Console.WriteLine("The gate is unlocked, so it is already closed!!!!!!!");
            }
        }

মেথড ৩: LockGate()

        public void LockGate()
        {
            if (CurrentState == Opened)
            {
                Console.WriteLine("Please Close the Gate first and then lock it..");
            }
            else if (CurrentState == Closed)
            {
                CurrentState = Locked;
                Console.WriteLine("The Gate has been locked successfully..");
            }
            else if (CurrentState == Locked)
            {
                Console.WriteLine("The gate is already locked, can not lock again");
            }
            else if (CurrentState == Unlocked)
            {
                CurrentState = Locked;
                Console.WriteLine("The gate went to  Locked state from Unlocked state...");
            }
         }

এবং সবশেষে মেথড ৪: UnlockGate()

        public void UnLockGate()
        {
            if (CurrentState == Opened)
            {
                Console.WriteLine("The gate is open, can not be unlocked.....");
            }
            else if (CurrentState == Closed)
            {
                Console.WriteLine("The gate is close, can not be unlocked....");
            }
            else if (CurrentState == Locked)
            {
                CurrentState = Closed;
                Console.WriteLine("The gate is unlocked, but it is closed...");
            }
            else if (CurrentState == Unlocked)
            {
                Console.WriteLine("The gate is unlocked already...Can not unlock again");
            }
        }

তাহলে ফলাফল গিয়ে দাঁড়াল আমাদের Gate Class টির চেহারা ঠিক এরকমঃ

public class Gate
    {
        private const int Opened = 1;
        private const int Closed = 2;
        private const int Locked = 3;
        private const int Unlocked = 4;
        public int CurrentState = Opened;

        public void OpenGate()
        {
            if (CurrentState == Opened)
            {
                Console.WriteLine("The gate is already open....");
            }
            else if (CurrentState == Closed)
            {
                CurrentState = Opened;
                Console.WriteLine("The gate state is now opened from closed state");
            }
            else if (CurrentState == Locked)
            {
                Console.WriteLine("Please unlock the gate and open it....");
            }
            else if (CurrentState == Unlocked)
            {
                CurrentState = Opened;
                Console.WriteLine("The gate is now unlocked and opened ");
            }
        }

        public void CloseGate()
        {
            if (CurrentState == Opened)
            {
                CurrentState = Closed;
                Console.WriteLine("The gate has been closed successfully........");

            }
            else if (CurrentState == Closed)
            {
                Console.WriteLine("The gate is already closed. You cannot close it again!!");
            }
            else if (CurrentState == Locked)
            {
                Console.WriteLine("The gate is locked. So you cannot close this gate!!");
            }
            else if (CurrentState == Unlocked)
            {
                Console.WriteLine("The gate is unlocked, so it is already closed!!!!!!!");
            }
        }

        public void LockGate()
        {
            if (CurrentState == Opened)
            {
                Console.WriteLine("Please Close the Gate first and then lock it..");
            }
            else if (CurrentState == Closed)
            {
                CurrentState = Locked;
                Console.WriteLine("The Gate has been locked successfully..");
            }
            else if (CurrentState == Locked)
            {
                Console.WriteLine("The gate is already locked, cannot lock again");
            }
            else if (CurrentState == Unlocked)
            {
                CurrentState = Locked;
                Console.WriteLine("The gate went to  Locked state from Unlocked state...");
            }
        }

        public void UnLockGate()
        {
            if (CurrentState == Opened)
            {
                Console.WriteLine("The gate is open, cannot be unlocked.....");
            }
            else if (CurrentState == Closed)
            {
                Console.WriteLine("The gate is close, cannot be unlocked....");
            }
            else if (CurrentState == Locked)
            {
                CurrentState = Closed;
                Console.WriteLine("The gate is unlocked, but it is closed...");
            }
            else if (CurrentState == Unlocked)
            {
                Console.WriteLine("The gate is unlocked already...Cannot unlock again");
            }
        }
    }

তাহলে আমরা ক্লায়েন্ট কোড লিখবো এভাবেঃ

    public class Program
    {
        public static void Main(string[] args)
        {
            var gate = new Gate();
            gate.CurrentState = 2;//2 is for Closed State
            gate.LockGate();
            Console.ReadLine();
        }
    }

আমরা এতক্ষণ যে কোড করলাম তা কারেন্ট সিনারিও এর জন্য পারফেক্টলি কাজ করবে । কিন্তু চিন্তা করুণ যদি Gate এর জন্য নতুন কোনও ফাংশনালিটি যোগ করার হয়ে পরে তখন আমাদের Existing কোড এ হাত দিতে হবে । এবং নতুন স্টেটটির জন্য পুরো একটা মেথড লিখতে হবে যেটি কিনা বিশাল বড় । এই অবস্থা থেকে পরিত্রাণ পেতে আমরা কিছু কাজ করতে পারি যেমনঃ

  • আমরা একটি স্টেট ইন্টারফেস বানাতে পারি যেটিতে Gate class এর সবএকশন গুলো মেথড আকারে থাকবে
  • এরপর আমরা প্রত্যেকটি মেথডের জন্য একটি করে Class বানাতে পারি । এখানে এক একটি Class এক একটি একশন সম্পাদন করবে ।
  • সবশেষে আমরা if else………… এ রকম কন্ডিশনাল লজিকগুলোকে বিতাড়িত করতে পারি এতে করে State Class গুলো এর মধ্যে এদের একশন গুলো ইমপ্লিমেন্ট করে নিবে ।

তাহলে আমরা প্রথমে Gate এর একশন গুলোকে একটি ইন্টারফেস এ নিয়ে যাই । ধরি ইন্টারফেসটির নাম IGateState:

      public interface IGateState
      {
        void OpenGate();
        void CloseGate();
        void LockGate();
        void UnLockGate();
      }

দেখা যাচ্ছে আমাদের চারটি একশন মেথড গুলোকে আমরা ইন্টারফেস এ পেয়ে গেছি । এখন এই চারটি একশন এর জন্য আমরা চারটি Class বানাবো যাতে করে একশন/মেথড এর ইমপ্লিমেন্টেশন আলাদা আলাদা Class এ থাকে ।এই চারটি Class এর সবাই আমাদের IGateState ইন্টারফেসটিকে ইমপ্লিমেন্ট করবে । তাহলে চলুন ঝটপট Class গুলো বানিয়ে ফেলিঃ

OpenState Class:

    public class OpenState : IGateState
    {
        public Gate Gate { get; set; }
        public OpenState(Gate gate)
        {
            Gate = gate;
        }
        public void OpenGate()
        {
            Console.WriteLine("The gate is already open....");
        }

        public void CloseGate()
        {
            Gate.CurrentState = 1;
            Console.WriteLine("The gate state is now opened from closed state");
        }

        public void LockGate()
        {
            Console.WriteLine("Please unlock the gate and open it....");
        }

        public void UnLockGate()
        {
            Gate.CurrentState = 1;
            Console.WriteLine("The gate is now unlocked and opened ");
        }
    }

CloseState Class:

    public class CloseState : IGateState
    {
        public Gate Gate { get; set; }
        public CloseState(Gate gate)
        {
            Gate = gate;
        }
        public void OpenGate()
        {
            Gate.CurrentState = 2;
            Console.WriteLine("The gate has been closed successfully........");
        }

        public void CloseGate()
        {
            Console.WriteLine("The gate is already closed. You cannot close it again!!");
        }

        public void LockGate()
        {
            Console.WriteLine("The gate is locked. So you cannot close this gate!!");
        }

        public void UnLockGate()
        {
            Console.WriteLine("The gate is unlocked, so it is already closed!!!!");
        }
    }

LockState Class:

public class LockState : IGateState
    {
        public Gate Gate { get; set; }
        public LockState(Gate gate)
        {
            Gate = gate;
        }
        public void OpenGate()
        {
            Console.WriteLine("Please Close the Gate first and then lock it..");
        }

        public void CloseGate()
        {
            Gate.CurrentState = 3;
            Console.WriteLine("The Gate has been locked successfully..");
        }

        public void LockGate()
        {
            Console.WriteLine("The gate is already locked, cannot lock again");
        }

        public void UnLockGate()
        {
            Gate.CurrentState = 3;
            Console.WriteLine("The gate went to  Locked state from Unlocked state...");
        }
    }

UnLockState Class:

public class UnLockState : IGateState
    {
        public Gate Gate { get; set; }
        public UnLockState(Gate gate)
        {
            Gate = gate;
        }
        public void OpenGate()
        {
            Console.WriteLine("The gate is open, cannot be unlocked.....");
        }

        public void CloseGate()
        {
            Console.WriteLine("The gate is close, cannot be unlocked....");
        }

        public void LockGate()
        {
            Gate.CurrentState = 2;
            Console.WriteLine("The gate is unlocked, but it is closed...");
        }

        public void UnLockGate()
        {
            Console.WriteLine("The gate is unlocked already...Cannot unlock again");
        }
    }

এখন আমরা আমাদের Gate Class এ ফিরে যাই এবং এতক্ষণ যা যা করলাম তা কাজে লাগাই । আমরা এর আগে গেট এর স্টেট গুলো ভ্যারিয়েবলে রেখে দিয়েছিলাম । এখন ভ্যারিয়েবলের পরিবর্তে আমরা আমাদের নতুন বানানো Class গুলো ব্যবহার করবো । তাহলে নতুন Gate Class টি এরকম দাঁড়াবেঃ

    public class Gate
    {
        public IGateState OpenedState;
        public IGateState ClosedState;
        public IGateState LockedState;
        public IGateState UnlockedState;
        public IGateState CurrentGateState { get; set; }

        public Gate()
        {
            OpenedState = new OpenState(this);
            ClosedState = new CloseState(this);
            LockedState = new LockState(this);
            UnlockedState = new UnLockState(this);
            CurrentGateState = OpenedState;
        }
        public void OpenGate()
        {
            CurrentGateState.OpenGate();
        }

        public void CloseGate()
        {
            CurrentGateState.CloseGate();
        }

        public void LockGate()
        {
            CurrentGateState.LockGate();
        }

        public void UnLockGate()
        {
            CurrentGateState.UnLockGate();
        }
    }

এবং আমাদের এর আগের State Class গুলো সামান্য চেঞ্জ হয়ে এরকম হবেঃ

OpenState Class:

    public class OpenState : IGateState
    {
        public Gate Gate { get; set; }
        public OpenState(Gate gate)
        {
            Gate = gate;
        }
        public void OpenGate()
        {
            Console.WriteLine("The gate is already open....");
        }

        public void CloseGate()
        {
            Gate.CurrentGateState = Gate.OpenedState;
            Console.WriteLine("The gate state is now opened from closed state");
        }

        public void LockGate()
        {
            Console.WriteLine("Please unlock the gate and open it....");
        }

        public void UnLockGate()
        {
            Gate.CurrentGateState = Gate.OpenedState;
            Console.WriteLine("The gate is now unlocked and opened ");
        }
    }

CloseState Class:

    public class CloseState : IGateState
    {
        public Gate Gate { get; set; }
        public CloseState(Gate gate)
        {
            Gate = gate;
        }
        public void OpenGate()
        {
            Gate.CurrentGateState = Gate.ClosedState;
            Console.WriteLine("The gate has been closed successfully........");
        }

        public void CloseGate()
        {
            Console.WriteLine("The gate is already closed. You cannot close it again!!");
        }

        public void LockGate()
        {
            Console.WriteLine("The gate is locked. So you cannot close this gate!!");
        }

        public void UnLockGate()
        {
            Console.WriteLine("The gate is unlocked, so it is already closed!!!!");
        }
    }

UnLockState Class:

public class UnLockState : IGateState
    {
        public Gate Gate { get; set; }
        public UnLockState(Gate gate)
        {
            Gate = gate;
        }
        public void OpenGate()
        {
            Console.WriteLine("The gate is open, cannot be unlocked.....");
        }

        public void CloseGate()
        {
            Console.WriteLine("The gate is close, cannot be unlocked....");
        }

        public void LockGate()
        {
            Gate.CurrentGateState = Gate.LockedState;
            Console.WriteLine("The gate is unlocked, but it is closed...");
        }

        public void UnLockGate()
        {
            Console.WriteLine("The gate is unlocked already...Cannot unlock again");
        }
    }

এরপর সবশেষে আমাদের ক্লায়েন্ট কোডে এই ডিজাইন প্যাটার্ন ইউজ করবো এভাবেঃ

   public class Program
    {
        public static void Main(string[] args)
        {
            var gate = new Gate();

            gate.CurrentGateState = gate.OpenedState;
            gate.OpenGate();
            gate.CloseGate();
            gate.LockGate();
            gate.UnLockGate();
            Console.ReadLine();
        }
    }

তো এই ছিলো আমাদের স্টেট ডিজাইন প্যাটার্ন ।

হ্যাপি কোডিং J


Iterator ডিজাইন প্যাটার্ন

মনে করুন একটি সফটওয়্যার ফার্ম ফেসবুক এবং টুইটারের যতগুলো ইউজার আছে তাদের সম্পূর্ণ তথ্যগুলো একটি কমন ডেটাবেইজ এ রাখতে চায় । (যদিও বাস্তবে ফেসবুক এবং টুইটার কখনও তাদের ইউজারের তথ্য বাইরের কাউকে দিবে না )

সফটওয়্যার ফার্মটি দুইজন ডেভেলপার কে এই কাজের দায়িত্ব দিল । একজন ডেভেলপার ফেসবুকের ইউজারের তথ্য নিয়ে কাজ করবে অন্যজন টুইটারের ইউজারের তথ্য নিয়ে কাজ করবে ।

প্রথম ডেভেলপার ফেসবুকের ইউজারদের একটি List এ রেখে কাজটি করে ফেললো এইভাবেঃ

public class Facebook
    {
        private List userList;
        public Facebook()
        {
            userList = new List();
            userList.Add("Logical Forhad");
            userList.Add("Rayhanur Rahman");
            userList.Add("Maruf Khan");
            userList.Add("Arif Raian");
            userList.Add("Mahedi Mahfuj");
            userList.Add("Atish Dipankar");
        }
          public List GetUserList()
         {
            return userList;
         }
    }

অন্যজন টুইটার ইউজার দের তথ্য গুলো একটি Array তে রেখে কাজটি করে ফেললো এইভাবেঃ

public class Twitter
    {
        private String[] userList;
        private const int MAX_USER = 6;
        public Twitter()
        {
            userList = new string[MAX_USER];
            userList[0] = "_iLogical";
            userList[1] = "Amit Seal Ami";
            userList[2] = "Crackbrained Sakkhor";
            userList[3] = "Bazlur Rahman Rokon";
            userList[4] = "Sachine Tendulkar";
            userList[5] = "Shane Watson";
        }

        public String[] GetUserList()
        {
            return userList;
        }
    }

এরপর যখন তারা ফেসবুক এবং টুইটারের ইউজারদের তথ্য গুলো ক্লায়েন্ট কোডে দেখতে চাইলো তখন তারা নিচের কোডের মত করে দেখে নিলোঃ

public class Program
    {
        static void Main(string[] args)
        {
            Facebook facebook = new Facebook();//concrete implementation
            List<string> fbuserList = facebook.GetUserList();

            Console.WriteLine("Facebook users........n");

            for (int i = 0; i < fbuserList.Count; i++)
            {
                Console.WriteLine(fbuserList[i]);
            }
            Console.WriteLine("n");

            Twitter twitter = new Twitter();//concrete implementation
            String[] twitterUserList = twitter.GetUserList();
            Console.WriteLine("Twitter users........n");

            for (int i = 0; i < twitterUserList.Length; i++)
            {
                Console.WriteLine(twitterUserList[i]);
            }

            Console.ReadKey();
        }
    }

এখন দুইজন ডেভেলপারই আলাদা আলাদা ডেটা স্ট্রাকচার ব্যবহার করেছে ফেসবুক এবং টুইটার এর ইউজারদের তথ্য রাখতে ।

অন্যদিকে  তারা তাদের কোড এর ডেটা স্ট্রাকচার চেঞ্জ করতে বিন্দু মাত্র আগ্রহী না। কিন্তু তারা যে ডিজাইন করেছে এই ডিজাইনে কিছু সমস্যা আছেঃ

  • আমরা টুইটার এবং ফেসবুকের কংক্রিট ইমপ্লিমেন্টেশন করছি, কিন্তু করার কথা ইন্টারফেস কোডিং , কেননা OOP বলে Code to interface not to implementation.
  • আমরা  ক্লায়েন্ট কোডে অনেকগুলো বয়লারপ্লেট কোড লিখে ফেলেছি । ফেসবুকের জন্য একবার এর ইউজারদের নাম লুপ চালিয়ে প্রিন্ট করেছি আর একবার টুইটারের জন্য ইউজারদের লিস্ট লুপ চালিয়ে প্রিন্ট করেছি ।মোদ্দা কথা একই কাজ দুই বার করেছি। গুগল প্লাসের ইউজারদের তথ্য প্রিন্ট করতে চাইলে আমাদের  আরেকটা লুপ যোগ করতে হবে ।
  • ক্লায়েন্ট কোডের জানতে হচ্ছে তাকে কত প্রকার ডেটাস্ট্রাকচারে লুপ ঘুরিয়ে ইউজারদের তথ্য প্রিন্ট করতে হবে। (যেমন গুগল প্লাস এর ইউজারদের তথ্য প্রিন্ট করতে চাইলে ক্লায়েন্ট কোড কে ফেসবুক+টুইটার+গুগল প্লাস = ৩ প্রকার ডেটাস্ট্রাকচার সম্পর্কে জানতে হবে ) যেটা OOP এর Encapsulation  রুল এর ভায়োলেশন ।

সমাধানঃ তাহলে আমরা এখন কি করতে পারি? চলুন একটু চিন্তা করি । আচ্ছা এরকম হলে কেমন হয় বলুন তো , একটি অবজেক্ট যদি যে কোনও প্রকার ডেটাস্ট্রাকচারের উপর কাজ করে সেই ডেটাস্ট্রাকচারের ডেটা গুলো প্রিন্ট করে দিতে পারে তাহলেই তো আমাদের কাজ হয়ে গেলো ।
তাহলে প্রথম ডেভেলপার এবং দ্বিতীয় ডেভেলপার অথবা অন্য যে কোনও ডেভেলপার যে ডেটাস্ট্রাকচার নিয়েই কাজ করুক না কেনো আমাদের ঐ একটি মাত্র অবজেক্ট সব ডেটাস্ট্রাকচারের উপর ছড়ি ঘুড়িয়ে আমাদের ডেটা গুলো প্রিন্ট করে দিতে পারে । হ্যা আমরা এখন এমনই একটি অবজেক্ট বানাবো ।

যেহেতু এটি সবার উপর কাজ করবে তাই এটিকে আমরা একটি ইন্টারফেস হিসেবেই রাখিঃ

public interface ISocialNetworking
    {
        bool HasNext();
        Object Next();
    }

এখন আমরা দুটি class বানাবো যারা আমাদের সদ্য নতুন বানানো ইন্টারফেসকে ইমপ্লিমেন্ট করবে ।

ফেসবুকের জন্যঃ

public class FacebookIterator : ISocialNetworking
    {
        private List<String> userList;
        private int _postion = 0;
        public FacebookIterator()
        {
            userList = new List<string>();
            userList.Add("Logical Forhad");
            userList.Add("Rayhanur Rahman");
            userList.Add("Maruf Khan");
            userList.Add("Arif Raian");
            userList.Add("Mahedi Mahfuj");
            userList.Add("Atish Dipankar");
        }
        public bool HasNext()
        {
            return userList.Count() > _postion;
        }

        public object Next()
        {
            String element = userList.ElementAt(_postion++);

            return element;
        }
    }

টুইটারের জন্যঃ

public class TwitterIterator : ISocialNetworking
    {
        private String[] userList;
        private const int MAX_USER = 6;
        private int position = 0;
        public TwitterIterator()
        {
            userList = new string[MAX_USER];
            userList[0] = "_iLogical";
            userList[1] = "Amit Seal Ami";
            userList[2] = "Crackbrained Sakkhor";
            userList[3] = "Bazlur Rahman Rokon";
            userList[4] = "Sachine Tendulkar";
            userList[5] = "Shane Watson";
        }

        public bool HasNext()
        {
            return userList.Length > position;
        }

        public object Next()
        {
            String element = userList[position++];
            return element;
        }
    }

এখন আমাদের ক্লায়েন্ট কোডে আমরা ইন্টারফেসটিকে এভাবে ইউজ করতে পারিঃ

public class Program
    {
        static void Main(string[] args)
        {

            Console.WriteLine("Facebook users........n");
            ISocialNetworking iterator = new FacebookIterator();
            while (iterator.HasNext())
            {
                Console.WriteLine(iterator.Next());
            }
            Console.WriteLine("n");

            iterator = new TwitterIterator();
            Console.WriteLine("Twitter users........n");
            while (iterator.HasNext())
            {
                Console.WriteLine(iterator.Next());
            }
            Console.ReadKey();
        }
    }

দাঁড়ান এখানেই শেষ না আমরা আমাদের ডিজাইনটিকে আরও ভালো করতে পারি । আমাদের Twitter class টীকে আমরা এভাবে মোডিফাই করতে পারি যেখানে আমরা একটি নতুন মেথড যোগ করেছি যার কাজ হচ্ছে আমাদের ইন্টারফেসটিকে(ISocialNetworking) রিটার্ন করা ।

সো মোডিফাইড Twitter class টি এরকমঃ

public class Twitter
    {
        private String[] userList;
        private const int MAX_USER = 6;
        public Twitter()
        {
            userList = new string[MAX_USER];
            userList[0] = "_iLogical";
            userList[1] = "Amit Seal Ami";
            userList[2] = "Crackbrained Sakkhor";
            userList[3] = "Bazlur Rahman Rokon";
            userList[4] = "Sachine Tendulkar";
            userList[5] = "Shane Watson";
        }

        public ISocialNetworking GetIterator()
        {
            return new TwitterIterator();
        }
    }

এবং মোডিফাইড Facebook class টি হচ্ছে এরকমঃ

public class Facebook
    {
        private List<String> userList;
        public Facebook()
        {
            userList = new List<string>();
            userList.Add("Logical Forhad");
            userList.Add("Rayhanur Rahman");
            userList.Add("Maruf Khan");
            userList.Add("Arif Raian");
            userList.Add("Mahedi Mahfuj");
            userList.Add("Atish Dipankar");
        }

        public ISocialNetworking GetIterator()
        {
            return new FacebookIterator();
        }
    }

এখন সবশেষে আমাদের ক্লায়েন্ট কোড চেঞ্জ হয়ে দাঁড়াবে এরকমঃ

public class Program
    {
        static void Main(string[] args)
        {
            ISocialNetworking iterator;

            Facebook facebook = new Facebook();
            Twitter twitter = new Twitter();

            iterator = facebook.GetIterator();

            Console.WriteLine("Facebook users........n");
            while (iterator.HasNext())
            {
                Console.WriteLine(iterator.Next());
            }
            Console.WriteLine("n");

            iterator = twitter.GetIterator();

            Console.WriteLine("Twitter users........n");
            while (iterator.HasNext())
            {
                Console.WriteLine(iterator.Next());
            }
            Console.ReadKey();
        }
    }

ব্যস হয়ে গেলো আমাদের Iterator ডিজাইন প্যাটার্ন । কিন্তু আমরা এই ছোট্ট ডিজাইন প্যাটার্ন শিখতে গিয়ে নিজে নিজেই আমাদের Iterator বানিয়ে নিয়েছি যেটা প্রায় সব ল্যাঙ্গুয়েজেই আগে থেকেই বানিয়ে রাখা আছে।
.NET এ এই ইন্টারফেসটির নাম হচ্ছে IEnumerator। ডেটাস্ট্রাকচার যে রকমই হোক না কেনো ,এটা সব Collection এর উপর কাজ করতে পারে।

এটি ব্যবহার করলে আমাদের FacebookIterator ,TwitterIterator এবং ISocialNetworking এগুলো আর লাগবে না  কিন্তু Facebook এবং Twitter class এ কিছুটা চেঞ্জ করতে হবে ।

মোডিফাইড  ফেসবুকঃ

public class Facebook
    {
        private List<String> userList;
        public Facebook()
        {
            userList = new List<string>();
            userList.Add("Logical Forhad");
            userList.Add("Rayhanur Rahman");
            userList.Add("Maruf Khan");
            userList.Add("Arif Raian");
            userList.Add("Mahedi Mahfuj");
            userList.Add("Atish Dipankar");
        }

        public IEnumerator GetIterator()//introducing IEnumurator
        {
            return userList.GetEnumerator();
        }
    }

মোডিফাইড টুইটারঃ

public class Twitter
    {
        private String[] userList;
        private const int MAX_USER = 6;
        public Twitter()
        {
            userList = new string[MAX_USER];
            userList[0] = "_iLogical";
            userList[1] = "Amit Seal Ami";
            userList[2] = "Crackbrained Sakkhor";
            userList[3] = "Bazlur Rahman Rokon";
            userList[4] = "Sachine Tendulkar";
            userList[5] = "Shane Watson";
        }

        public IEnumerator GetIterator()//introducing IEnumurator
        {
            return userList.GetEnumerator();
        }
    }

এবং সবশেষে আমাদের ক্লায়েন্ট কোড সামান্য একটু মোডিফাইড হয়ে এরকম হবেঃ

public class Program
    {
        static void Main(string[] args)
        {
            IEnumerator iterator;//changed 

            Facebook facebook = new Facebook();
            Twitter twitter = new Twitter();

            iterator = facebook.GetIterator();

            Console.WriteLine("Facebook users........n");
            while (iterator.MoveNext())
            {
                Console.WriteLine(iterator.Current);
            }
            Console.WriteLine("n");

            iterator = twitter.GetIterator();

            Console.WriteLine("Twitter users........n");
            while (iterator.MoveNext())
            {
                Console.WriteLine(iterator.Current);
            }
            Console.ReadKey();
        }
    }

তাহলে এই ছিলো আমাদের Iterator ডিজাইন প্যাটার্ন । খুবই সহজ এবং ছোট একটি ডিজাইন প্যাটার্ন যেটি যে কোনও Collection এর উপর হুকুমদারী করতে পারে 🙂

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