আমারা একটা ছোট কনসল অ্যাপ্লিকেশন এর মাধ্যমে  কন্ডিশন/ IF-ELSE এর পরিবর্তে পলিমরফিজম ও সিম্পল ফ্যাক্টরি প্যাটার্নের ব্যবহার (RIP) কিভাবে করা যায় তা দেখব। এখানে আমাদের একটা অ্যাপ্লিকেশন আছে যার কাজ ব্যাঙ্কের ডাটা মাইগ্রেশন করা, AbcBank এর জন্য আমরা এটা তৈরি করেছি যা শুধুমাত্র কাস্টমার  ইনফরমেশন মাইগ্রেট করতে পারে।

public class AbcBank 
{
    public void PerformEtl()
    {
        Console.WriteLine("Customer info migration is starting!");
        /*some precess here */
        Console.WriteLine("Customer info migration is done!");
    }
}
class Program
{   
    static async Task Main(string[] args)
    {
        await Task.Delay(100);

        AbcBank bank = new AbcBank();
        bank.PerformEtl();

        Console.ReadLine();
    }
}

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

public abstract class BaseBank
{
    public abstract void PerformEtl();
}
public class AbcBank : BaseBank
{
    public override void PerformEtl()
    {
        Console.WriteLine("Customer info migration is starting!");
        /*some precess here */
        Console.WriteLine("Customer info migration is done!");
    }
}
public class PqrBank : BaseBank
{
    public override void PerformEtl()
    {
        Console.WriteLine("Customer info migration is staring!");
        /* some precess here */
        Console.WriteLine("Customer info migration is done!");
       
        Console.WriteLine("Account info migration is staring!");
        /*some precess here */
        Console.WriteLine("Account info migration is done!");
    }
}
class Program
{   
    static async Task Main(string[] args)
    {
        await Task.Delay(100);

        BaseBank bank = null; ;
        if (AppConstants.Bank == Enums.Companies.AbcBank.ToString())
        {
            bank = new AbcBank();
        } 
        else if (AppConstants.Bank == Enums.Companies.PqrBank.ToString()) 
        {
            bank = new PqrBank();
        }

        bank.PerformEtl();

        Console.ReadLine();
    }
}

এরপর ব্যাংক XyzBank এই অ্যাপ্লিকেশনের প্রতি আগ্রহী হয় যাদের মাইগ্রেশন প্রক্রিয়াটাও অন্যদের চেয়ে একটু ভিন্ন হবে। আমরা সহজেই একটা নতুন ক্লাস নিয়ে তাতে মাইগ্রেশন প্রক্রিয়াটা ইমপ্লিমেন্ট করে একটা ELSE-IF এর মাধ্যমে মাইন মেথডে অ্যাড করে দিতে পারি। যাতে অন্য ক্লাস গুলো বা তাদের মাইগ্রেশন প্রক্রিয়ায় কোন পরিবরতন না হয়।

public class XyzBank : BaseBank
{
    public override void PerformEtl()
    {
        Console.WriteLine("Customer info migration is staring!");
        /* some precess here */
        Console.WriteLine("Customer info migration is done!");

        Console.WriteLine("Account info migration is staring!");
        /*some precess here */
        Console.WriteLine("Account info migration is done!");

        Console.WriteLine("Transaction info migration is staring!");
        /*some precess here */
        Console.WriteLine("Transaction info migration is done!");
    }
}
class Program
{   
    static async Task Main(string[] args)
    {
        await Task.Delay(100);

        BaseBank bank = null; ;
        if (AppConstants.Bank == Enums.Companies.AbcBank.ToString())
        {
            bank = new AbcBank();
        } 
        else if (AppConstants.Bank == Enums.Companies.PqrBank.ToString()) 
        {
            bank = new PqrBank();
        }
        else if (AppConstants.Bank == Enums.Companies.XyzBank.ToString())
        {
            bank = new XyzBank();
        }

        bank.PerformEtl();

        Console.ReadLine();
    }

}

আপাত দৃষ্টিতে মনে হচ্ছে, এই পর্যন্ত সব কিছু ঠিকঠাকই আছে! কিন্তু যদি আমার ভাল করে লক্ষ্য করি তাহলে দেখতে পারি যে IF বা ELSE-IF এর সংখ্যা বেড়েই যাছে , তাছাড়া মেইন ফাংশন SOLID এর Single-responsibility principle কে মানতে পারছে না, PerformEtl কে কল করা ছাড়াও তাকে  কন্ডিশন অনুসারে ব্যাংক অবজেক্ট তৈরি করতে হচ্ছে ।

এখন আমারা যেটা করতে পারি তাহল অবজেক্ট  তৈরির কাজটা অন্য একটা class দেয়া যার মাধ্যমে কনফিগারেশন অনুযায়ী কাঙ্ক্ষিত অবজেক্টকে পেতে পারি , এর জন্য আমরা সিম্পল ফ্যাক্টরি প্যাটার্ন  ব্যবহার করব।  ফ্যাক্টরি প্যাটার্ন হল একটি ডিজাইন প্যাটার্ন যেটি ক্রিয়েশনাল প্যাটার্ন এর অন্তর্গত।  Gang of Four এর সংজ্ঞা অনুসারে  ফ্যাক্টরি প্যাটার্ন মুলত দুই প্রকারঃ Factory Method ও Abstract Factory। এই উদাহরনে আমরা সিম্পল ফ্যাক্টরির প্যাটার্ন  ব্যবহার করব যা পরবর্তীতে Factory Method ও Abstract Factory  বুঝতে সাহায্য করবে।

ফ্যাক্টরি ক্লাসঃ

// Simple Factory pattern
public static class BankFactory
{
    public static Dictionary<string, BaseBank> banks 
        = new Dictionary<string, BaseBank>();

    static BankFactory()
    {
        //banks.Add(Enums.Companies.AbcBank.ToString(), new AbcBank());
        //banks.Add(Enums.Companies.PqrBank.ToString(), new PqrBank());
        //banks.Add(Enums.Companies.XyzBank.ToString(), new XyzBank());
    }
    public static BaseBank Create(string name)
    {
        // Lazy Loading pattern
        if (!banks.Any())
        {
            banks.Add(Enums.Companies.AbcBank.ToString(), new AbcBank());
            banks.Add(Enums.Companies.PqrBank.ToString(), new PqrBank());
            banks.Add(Enums.Companies.XyzBank.ToString(), new XyzBank());
        }
        // RIP(replace if with polymorphism) pattern
        return banks[name];
    }

    public static BaseBank Create2(string name)
    {

        var collection = new ServiceCollection();
        collection.AddTransient<BaseBank, AbcBank>();
        collection.AddScoped<BaseBank, PqrBank>();
        collection.AddScoped<BaseBank, XyzBank>();
        
        var serviceProvider = collection.BuildServiceProvider();
        var bank = serviceProvider.GetServices<BaseBank>()
                  .FirstOrDefault(w=> w.GetType().Name == name);

        return bank;
    }

}

মেইন মেথডঃ

class Program
{
    
    static async Task Main(string[] args)
    {
        await Task.Delay(100);

        BaseBank bank = BankFactory.Create(AppConstants.Bank);
        bank.PerformEtl();

        Console.ReadLine();
    }
}

এখন কনফিগারেশন অনুযায়ী ব্যাংক অবজেক্ট তৈরি করবে ফ্যাক্টরি আর মেইন মেথড শুধু মাত্র PerformEtl কে কল করে দেবে আর Runtime polymorphism  বা Method overriding এর মাধ্যমে ব্যাংক অনুযায়ী PerformEtl কাজ করবে।  Create2 মেথডে ডিপেন্ডেন্সি ইনজেকশন এর মাধ্যমে কি ভাবে করা যায় তা দেখানো হয়েছে এটাও একটা বিকল্প হতে পারে।

আর বাঙ্কের লিস্টটাকে কন্সট্রাক্টর এ পপুলেট না করে Create মেথডে করা হয়েছে তার মানে যখন Create মেথড কে কল করা হবে ঠিক তখনি বাঙ্কের লিস্টটা  পপুলেট  হবে যদি এর আগে পপুলেট  না হয়ে থাকে, একে  Lazy loading বা Lazy initialization বলা হয়ে থাকে।

কন্ডিশন IF ELSE এর পরিবর্তে পলিমরফিজম ও ফ্যাক্টরি প্যাটার্নের ব্যবহার ২ কন্ডিশন (IF-ELSE) এর পরিবর্তে পলিমরফিজম ও ফ্যাক্টরি প্যাটার্নের ব্যবহার

কন্ডিশন (IF-ELSE) এর পরিবর্তে পলিমরফিজম ও ফ্যাক্টরি প্যাটার্নের ব্যবহার ২

আবার নতুন একটা ব্যাংক ইউজার হিসাবে আসলে আমরা তার জন্য নতুন একটা class লিখব যেটি BaseBank কে ইমপ্লিমেন্ট করবে আর তাকে ফ্যাক্টরির ডিকশনারীতে অ্যাড করে দেব অথবা যদি  Create2 মেথড ব্যবহার করি তবে সার্ভিস কালেকশনে অ্যাড করে দেব  মেইন মেথড বা অন্যান্য class  এ আর কোন পরিবর্তনের দরকার হবে না। এখন যদি কোন ব্যাঙ্কের PerformEtl মেথডে পরিবরতন আনতে হয় আমারা শুধু সে ব্যাঙ্কের class/method  হতে পরিবর্তন করতে পারব, এই পরিবর্তন অন্য কোন ব্যাঙ্কের PerformEtl এ প্রভাব ফেলবে না এবং আপ্লিকেশনটি  Loosely coupled এবং Highly cohesive হবে। আশা করি কিভাবে ধাপে ধাপে Design Patterns ও Principles ইমপ্লিমেন্ট করতে হয় তার কিছুটা ধারনা আমরা পেয়েছি।
সোর্স কোড: github.com/Khairultaher