Builder Design Pattern

ধরুন, আপনি একটি রেস্টুরেন্টে কাস্টম বার্গার খেতে গেছেন। আপনি ওয়েটারকে বললেন, "আমাকে একটি বার্গার দিন।" কিন্তু ওয়েটার আপনাকে একের পর এক প্রশ্ন করা শুরু করল:

  • ব্রেড কোনটা লাগবে? সাদা, গমের নাকি সিড ব্রেড?

  • প্যাটিটা কী হবে? গরুর মাংস, মুরগি, নাকি ভেজিটেবল?

  • চিজ চাই কি না?

  • সস কেমন হবে?

  • অতিরিক্ত (Extra) কিছু লাগবে কি?

এখন, কোডিংয়ের দুনিয়ায় যদি এমন একটি ক্লাস থাকে যার ১০টি প্রপার্টি বা বৈশিষ্ট্য আছে এবং অবজেক্ট তৈরির সময় প্রতিবার কনস্ট্রাক্টরে (Constructor) এই ১০টি প্যারামিটার পাস করতে হয়, তবে বিষয়টি অত্যন্ত বিরক্তিকর এবং ভুল হওয়ার সম্ভাবনায় ভরপুর হয়ে যায়। সফটওয়্যার ইঞ্জিনিয়ারিংয়ে একে বলা হয় "Telescoping Constructor Anti-Pattern"।

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

Builder Pattern কী?

Builder Pattern হলো একটি Creational Design Pattern, যা একটি জটিল অবজেক্টকে ধাপে ধাপে (Step-by-step) তৈরি করতে সাহায্য করে। এই প্যাটার্ন অবজেক্টের কনস্ট্রাকশন প্রসেসকে তার রিপ্রেজেন্টেশন থেকে আলাদা করে ফেলে। ফলে একই কনস্ট্রাকশন প্রসেস ব্যবহার করে বিভিন্ন ধরনের অবজেক্ট তৈরি করা সম্ভব হয়।

কেন এবং কখন Builder Pattern ব্যবহার করবেন? (Deep Dive)

১. Telescoping Constructor এড়াতে: যখন একটি ক্লাসে অনেকগুলো ফিল্ড থাকে (যার মধ্যে কিছু আবশ্যিক এবং কিছু অপশনাল), তখন বিশাল বড় কনস্ট্রাক্টর ফাংশন লেখা বা অগণিত null পাস করা থেকে বাঁচতে এই প্যাটার্ন ব্যবহৃত হয়। ২. Immutability (অপরিবর্তনশীলতা) নিশ্চিত করতে: অবজেক্ট একবার তৈরি হয়ে যাওয়ার পর কেউ যেন এর ডেটা পরিবর্তন করতে না পারে, তা নিশ্চিত করার জন্য অবজেক্টের কোনো Setter মেথড না রেখে Builder-এর মাধ্যমে অবজেক্ট তৈরি করা একটি বেস্ট প্র্যাকটিস। ৩. Method Chaining: বিল্ডার প্যাটার্নে প্রতিটি মেথড বিল্ডারের নিজস্ব ইনস্ট্যান্স রিটার্ন করে, যার ফলে কোড দেখতে খুব সুন্দর হয় এবং এক লাইনেই চেইন (Chain) করে সব ডেটা সেট করা যায় (যাকে Fluent Interface বলা হয়)।

Java Code Example: Builder Pattern ইমপ্লিমেন্টেশন

নিচে জাভা (Java) ব্যবহার করে কাস্টম বার্গার তৈরির একটি প্রফেশনাল Builder Pattern-এর উদাহরণ দেওয়া হলো। এখানে একটি স্ট্যাটিক ইনার ক্লাস (Static Inner Class) ব্যবহার করা হয়েছে, যা জাভাতে বিল্ডার প্যাটার্ন লেখার সবচেয়ে স্ট্যান্ডার্ড নিয়ম।

// Step 1: The Product Class
class Burger {
    private final String bread;
    private final String patty;
    private final boolean hasCheese;
    private final String sauce;

    // Private constructor ensures the object can only be created via the Builder
    private Burger(BurgerBuilder builder) {
        this.bread = builder.bread;
        this.patty = builder.patty;
        this.hasCheese = builder.hasCheese;
        this.sauce = builder.sauce;
    }

    public void showDetails() {
        System.out.println("Burger Configuration:");
        System.out.println("Bread: " + bread);
        System.out.println("Patty: " + patty);
        System.out.println("Cheese: " + (hasCheese ? "Yes" : "No"));
        System.out.println("Sauce: " + sauce);
    }

    // Step 2: The Builder Class (Static Inner Class)
    public static class BurgerBuilder {
        private String bread;
        private String patty;
        private boolean hasCheese = false; // Default value
        private String sauce = "None";     // Default value

        // Methods to set properties, returning the builder itself for chaining
        public BurgerBuilder setBread(String bread) {
            this.bread = bread;
            return this;
        }

        public BurgerBuilder setPatty(String patty) {
            this.patty = patty;
            return this;
        }

        public BurgerBuilder addCheese() {
            this.hasCheese = true;
            return this;
        }

        public BurgerBuilder setSauce(String sauce) {
            this.sauce = sauce;
            return this;
        }

        // Final step: Build and return the actual product
        public Burger build() {
            // Can add validation logic here before object creation
            if (this.bread == null || this.patty == null) {
                throw new IllegalStateException("Bread and Patty are required!");
            }
            return new Burger(this);
        }
    }
}

// Step 3: Client Code
public class Main {
    public static void main(String[] args) {
        // Step-by-step object creation using Method Chaining
        Burger customBurger = new Burger.BurgerBuilder()
                .setBread("Wheat Bread")
                .setPatty("Beef")
                .addCheese()
                .setSauce("BBQ")
                .build();

        customBurger.showDetails();
    }
}

আধুনিক ফ্রেমওয়ার্কগুলোতে Builder Pattern-এর ব্যবহার

তুমি যদি মডার্ন ওয়েব ডেভেলপমেন্ট নিয়ে কাজ করো, তবে দেখবে ফ্রেমওয়ার্কের কোর আর্কিটেকচারে Builder Pattern কতটা গভীরভাবে মিশে আছে।

Django তে QuerySet API

লারাভেলের মতো জ্যাঙ্গোতেও (Django) ORM (Object-Relational Mapping)-এর QuerySet API মূলত Builder Pattern-এর কনসেপ্ট মেনেই কাজ করে।

# Python/Django ORM QuerySet
User.objects.filter(status='active').exclude(is_deleted=True).order_by('name')

এখানেও মেথড চেইনিংয়ের মাধ্যমে ধাপে ধাপে কোয়েরি ফিল্টার করা হচ্ছে।

Java এবং Spring Boot এ ব্যবহার

জাভা ইকোসিস্টেমে এই প্যাটার্নটি অত্যন্ত জনপ্রিয়।

  • Lombok Library: জাভাতে @Builder অ্যানোটেশন ব্যবহার করলেই ব্যাকগ্রাউন্ডে স্বয়ংক্রিয়ভাবে একটি বিল্ডার ক্লাস তৈরি হয়ে যায়।

  • Spring Framework: স্প্রিং বুটে সিকিউরিটি কনফিগার করা (HttpSecurity), অথবা রেস্ট এপিআইয়ের জন্য ইউআরএল তৈরি করার ক্ষেত্রে (UriComponentsBuilder) বিল্ডার প্যাটার্ন ব্যাপকভাবে ব্যবহৃত হয়।

সংক্ষেপে, যখনই তোমার সিস্টেমে এমন কোনো অবজেক্ট বানানোর প্রয়োজন হবে যার অনেকগুলো ভ্যারিয়েশন বা অপশনাল প্যারামিটার আছে, তখনই কনস্ট্রাক্টরের ঝামেলায় না গিয়ে সরাসরি Builder Pattern ব্যবহার করবে। এতে কোড রিডেবল, ক্লিন এবং প্রোডাকশন-রেডি হবে!