Builder Design Pattern සරලව - සංකීර්ණ Object හැදීමේ රහස (Lankan Dev Guide)

Builder Design Pattern සරලව - සංකීර්ණ Object හැදීමේ රහස (Lankan Dev Guide)

ඉතින් කොහොමද යාලුවනේ? සොෆ්ට්වෙයාර් ඉංජිනේරු ක්ෂේත්‍රයේ ඉන්න අපිට, දවසින් දවස අලුත් දේවල් ඉගෙන ගන්න සිද්ධ වෙනවා. විශේෂයෙන්ම, අපේ code එක ලස්සනට, පිළිවෙලට, පහසුවෙන් තේරුම් ගන්න පුළුවන් විදිහට ලියන්න ඕන නම්, Design Patterns කියන එක ගැන දැනගෙන ඉන්න එක අත්‍යවශ්‍යයි. අද මම ඔයාලත් එක්ක කතා කරන්න යන්නේ ඒ වගේම ගොඩක් වැදගත් වෙන, Builder Design Pattern එක ගැන.

හිතන්නකෝ ඔයාලා ගොඩක් properties (ගුණාංග) තියෙන Object එකක් create කරන්න හදනවා කියලා. උදාහරණයක් විදිහට, Laptop එකක්, Car එකක්, නැත්නම් User Profile එකක්. මේ වගේ වෙලාවට, ඒ Object එකේ constructor එකට ගොඩක් parameters යවන්න වෙනවා. සමහර වෙලාවට ඒ parameters වලින් හැම එකක්ම අනිවාර්යය නැති වෙන්නත් පුළුවන්. එතකොට, ඒ constructor එක දැක්කම ඇස් පේන්නෙ නැති වෙන තරමට දිගයි, අවුල් සහගතයි. ඒ වගේම, මොකක්ද මේ parameter එකෙන් කරන්නේ කියලා හොයාගන්නත් අමාරුයි. Object එකක් create කරන එක මෙච්චර අමාරු දෙයක්ද? ඔන්න ඕකට තමා Builder Pattern එක පිහිටට එන්නේ.

Builder Pattern එක කියන්නේ මොකක්ද?

සරලවම කියනවා නම්, Builder Pattern එක කියන්නේ Design Pattern එකක්, complex objects (සංකීර්ණ Object) හදන විදිය සරල කරන්න උදව් කරන. මේ pattern එකෙන් කරන්නේ, Object එකක් හදන ක්‍රියාවලිය (construction process) ඒ Object එකෙන් වෙන් කරන එක. ඒ කියන්නේ, Object එකේ තියෙන හැම property එකක්ම එක පාරක් constructor එකට දෙනවා වෙනුවට, පියවරෙන් පියවර, වෙනම Builder කෙනෙක් හරහා Object එක හදන එක.

හිතන්නකෝ ඔයාලා ගෙයක් හදනවා කියලා. ගේ හදන්න පුළුවන් එක පාරටම හැම දේම දාලා ලොකු constructor එකක් හරහා නෙවෙයි. මුලින්ම ප්ලෑන් එක හදනවා, ඊට පස්සේ පදනම දානවා, බිත්ති ටික හදනවා, වහලය දානවා, පාට කරනවා... මේ හැම දේම කරන්නේ වෙන වෙනම steps වලට. මේ steps ටික කරන්නේ Builder කෙනෙක්, ඒ කියන්නේ ගේ හදන බාස් කෙනෙක් වගේ කෙනෙක්. අන්තිමට, හැම step එකක්ම ඉවර වුණාම complete වුණ ගේ (Product) එක අපිට ලැබෙනවා.

Builder Pattern එකේ ප්‍රධාන කොටස් තුනක් තියෙනවා:

  1. Product: හදන්න අවශ්‍ය Object එක (අපේ උදාහරණයේ ගේ).
  2. Builder: Product එක හදන interface එකක් (හෝ abstract class එකක්) සහ concrete Builder එකක් (බාස් කෙනෙක්). මේ Builder එකේ තමයි Product එකේ parts ටික එකතු කරන methods තියෙන්නේ.
  3. Director: Builder එක use කරලා Product එක හදන කෙනා (ගෙදර අයිතිකාරයා හෝ ඉංජිනේරුවා). මේක optional කොටසක්, හැම වෙලේම අවශ්‍ය වෙන්නේ නැහැ.

Builder Pattern එකෙන් විසඳන ගැටලුව

ඔයාලා ෂුවර් එකටම දැකලා ඇති මේ වගේ code එකක්:


public class Laptop {
    private String cpu;
    private int ramGB;
    private int storageGB;
    private String gpu;
    private String os;
    private double screenSizeInches;
    private int batteryWHr;
    private boolean webcamIncluded;
    private boolean fingerprintReader;

    public Laptop(String cpu, int ramGB, int storageGB, String gpu, String os,
                  double screenSizeInches, int batteryWHr, boolean webcamIncluded,
                  boolean fingerprintReader) {
        this.cpu = cpu;
        this.ramGB = ramGB;
        this.storageGB = storageGB;
        this.gpu = gpu;
        this.os = os;
        this.screenSizeInches = screenSizeInches;
        this.batteryWHr = batteryWHr;
        this.webcamIncluded = webcamIncluded;
        this.fingerprintReader = fingerprintReader;
    }

    // Getters and other methods...
}

// මේක use කරන විදිය
Laptop myGamingLaptop = new Laptop(
    "Intel Core i9",    // CPU
    32,                 // RAM in GB
    1024,               // Storage in GB
    "NVIDIA RTX 4080",  // GPU
    "Windows 11 Home",  // OS
    17.3,               // Screen Size in inches
    90,                 // Battery in WHr
    true,               // Webcam included
    true                // Fingerprint Reader included
);

// සමහර Properties නැත්නම්?
Laptop officeLaptop = new Laptop(
    "Intel Core i5",    // CPU
    8,                  // RAM in GB
    256,                // Storage in GB
    null,               // GPU (If not needed, or default)
    "Windows 10 Pro",   // OS
    15.6,               // Screen Size in inches
    50,                 // Battery in WHr
    true,               // Webcam included
    false               // Fingerprint Reader (false if not included)
);

මේ code එක දැක්කම මොකද හිතෙන්නේ? constructor එක මාර විකාරයක් නේද? මේක අපි හඳුන්වන්නේ "Telescopic Constructor" anti-pattern එක කියලා. ගැටලු ගණනාවක් තියෙනවා:

  • කියවන්න අමාරුයි: parameters ගොඩක් තියෙන නිසා මොකක්ද මොකක්ද කියලා හොයාගන්න අමාරුයි.
  • වැරදෙන්න තියෙන ඉඩ වැඩියි: parameters ටිකේ order එක මාරු වුනොත් bug එනවා. උදාහරණයක් විදිහට ramGB වෙනුවට storageGB දුන්නොත්.
  • නොගැලපීම් (Inconsistency): optional parameters වලට null නැත්නම් 0 වගේ default දාන්න වෙනවා, ඒකෙන් code එකේ කියවීමේ හැකියාව අඩු වෙනවා.
  • maintain කරන්න අමාරුයි: අලුත් property එකක් add කරද්දී, constructor එකයි ඒක call කරන හැම තැනක්ම update කරන්න වෙනවා.

ඔන්න ඔය වගේ ගැටලු වලට තමයි Builder Pattern එකෙන් විසඳුම් දෙන්නේ.

Builder Pattern එක ක්‍රියාත්මක වන ආකාරය (උදාහරණයක් සමග)

අපි කලින් ගත්ත Laptop උදාහරණයම පාවිච්චි කරමු. අපිට අවශ්‍යයි Laptop එකක් හදන්න පුළුවන් වෙන්න, ඒකේ CPU, RAM, Storage වගේ required දේවල් අනිවාර්යයෙන්ම දීලා, GPU, OS, Screen Size වගේ optional දේවල් අවශ්‍ය නම් විතරක් add කරන්න.

1. Product Class එක (Laptop)

අපේ Laptop class එකේ properties ටික private final විදිහට තියෙනවා. constructor එක private කරලා, ඒකට LaptopBuilder object එකක් විතරක් ගන්නවා. ඒ කියන්නේ, Laptop object එකක් හදන්න පුළුවන් වෙන්නේ Builder කෙනෙක් හරහා විතරයි. මේකෙන් immutability එකටත් උදව් වෙනවා.


public class Laptop {
    private final String cpu;
    private final int ramGB;
    private final int storageGB;
    private final String gpu;
    private final String os;
    private final double screenSizeInches;
    private final int batteryWHr;
    private final boolean webcamIncluded;
    private final boolean fingerprintReader;

    // Private constructor - can only be called by the Builder
    private Laptop(LaptopBuilder builder) {
        this.cpu = builder.cpu;
        this.ramGB = builder.ramGB;
        this.storageGB = builder.storageGB;
        this.gpu = builder.gpu;
        this.os = builder.os;
        this.screenSizeInches = builder.screenSizeInches;
        this.batteryWHr = builder.batteryWHr;
        this.webcamIncluded = builder.webcamIncluded;
        this.fingerprintReader = builder.fingerprintReader;
    }

    // Getters for all fields (no setters for immutability)
    public String getCpu() { return cpu; }
    public int getRamGB() { return ramGB; }
    public int getStorageGB() { return storageGB; }
    public String getGpu() { return gpu; }
    public String getOs() { return os; }
    public double getScreenSizeInches() { return screenSizeInches; }
    public int getBatteryWHr() { return batteryWHr; }
    public boolean isWebcamIncluded() { return webcamIncluded; }
    public boolean isFingerprintReader() { return fingerprintReader; }

    @Override
    public String toString() {
        return "Laptop [CPU=" + cpu + ", RAM=" + ramGB + "GB, Storage=" + storageGB + "GB, GPU=" + gpu +
               ", OS=" + os + ", Screen=" + screenSizeInches + "in, Battery=" + batteryWHr + "WHr, Webcam=" +
               webcamIncluded + ", Fingerprint=" + fingerprintReader + "]";
    }

2. Builder Class එක (LaptopBuilder)

මේ තමයි වැඩේ කරන කෙනා. මේක Laptop class එක ඇතුලේ static nested class එකක් විදිහට තියෙන්නේ. මේකේ තමයි අපේ Laptop එකේ properties set කරන්න පුළුවන් methods ටික තියෙන්නේ. හැම method එකකින්ම current Builder instance එක (this) return කරන නිසා, අපිට පුළුවන් methods ටික chain කරන්න.


    public static class LaptopBuilder {
        // Required fields
        private String cpu;
        private int ramGB;
        private int storageGB;

        // Optional fields with default values
        private String gpu = "Integrated";
        private String os = "Windows";
        private double screenSizeInches = 15.6;
        private int batteryWHr = 50;
        private boolean webcamIncluded = true;
        private boolean fingerprintReader = false;

        // Constructor for required fields
        public LaptopBuilder(String cpu, int ramGB, int storageGB) {
            this.cpu = cpu;
            this.ramGB = ramGB;
            this.storageGB = storageGB;
        }

        // Setter methods for optional fields, returning the builder itself for chaining
        public LaptopBuilder withGPU(String gpu) {
            this.gpu = gpu;
            return this;
        }

        public LaptopBuilder withOS(String os) {
            this.os = os;
            return this;
        }

        public LaptopBuilder withScreenSize(double screenSizeInches) {
            this.screenSizeInches = screenSizeInches;
            return this;
        }

        public LaptopBuilder withBattery(int batteryWHr) {
            this.batteryWHr = batteryWHr;
            return this;
        }

        public LaptopBuilder includesWebcam(boolean webcamIncluded) {
            this.webcamIncluded = webcamIncluded;
            return this;
        }

        public LaptopBuilder includesFingerprintReader(boolean fingerprintReader) {
            this.fingerprintReader = fingerprintReader;
            return this;
        }

        // Build method to create the final Laptop object
        public Laptop build() {
            // Optional: Add validation logic here before creating the object
            if (ramGB < 4) {
                throw new IllegalStateException("RAM must be at least 4GB.");
            }
            if (storageGB < 128) {
                throw new IllegalStateException("Storage must be at least 128GB.");
            }
            return new Laptop(this);
        }
    }
}

3. Director (භාවිතය)

මේ pattern එක use කරන එක මාර ලේසියි. අපිට අවශ්‍ය විදියට Laptop object එකක් හදන්න පුළුවන්, readable විදියට.


public class Main {
    public static void main(String[] args) {
        // Gaming Laptop එකක් හදනවා - හැම option එකක්ම වගේ දාලා
        Laptop gamingLaptop = new Laptop.LaptopBuilder("Intel Core i9", 32, 1024)
                                .withGPU("NVIDIA RTX 4080")
                                .withOS("Windows 11 Home")
                                .withScreenSize(17.3)
                                .withBattery(90)
                                .includesWebcam(true)
                                .includesFingerprintReader(true)
                                .build();
        System.out.println("Gaming Laptop: " + gamingLaptop);

        System.out.println("\n-----------------------------------\n");

        // Office Laptop එකක් හදනවා - අවශ්‍යම දේවල් විතරක් දීලා, අනිත්වා default වලින්
        Laptop officeLaptop = new Laptop.LaptopBuilder("AMD Ryzen 5", 8, 256)
                                .withOS("Ubuntu Linux")
                                .includesFingerprintReader(true) // කැමරාව default true නිසා වෙනම කියන්න ඕන නෑ
                                .build();
        System.out.println("Office Laptop: " + officeLaptop);

        System.out.println("\n-----------------------------------\n");

        // Basic Laptop එකක් හදනවා - අනිවාර්යය දේවල් විතරක් දීලා
        Laptop basicLaptop = new Laptop.LaptopBuilder("Intel Celeron", 4, 128)
                                .build(); // අනිත් හැම දෙයක්ම default values
        System.out.println("Basic Laptop: " + basicLaptop);

        System.out.println("\n-----------------------------------\n");

        // Validation error එකක් පෙන්වන හැටි
        try {
            Laptop errorLaptop = new Laptop.LaptopBuilder("Old Processor", 2, 64)
                                    .build();
            System.out.println(errorLaptop);
        } catch (IllegalStateException e) {
            System.out.println("Error creating laptop: " + e.getMessage());
        }
    }
}

මේකෙන් පේනවා නේද code එක කොච්චර clean ද, readable ද කියලා? required parameters ටික මුලින්ම දීලා, ඊට පස්සේ optional parameters ටික අපිට අවශ්‍ය විදිහට එකතු කරන්න පුළුවන්. method names වලින් මොකක්ද කරන්නේ කියලා පැහැදිලිව පේනවා.

Builder Pattern එකේ වාසි

Builder Pattern එක පාවිච්චි කරන එකෙන් අපිට ලැබෙන වාසි ගොඩක් තියෙනවා:

  • කියවීමේ හැකියාව වැඩි වීම (Improved Readability): Object එකක් හදන විදිය පැහැදිලියි. method chaining නිසා code එක natural language වගේ කියවන්න පුළුවන්.
  • බහුල Parameters පහසුවෙන් හැසිරවීම (Handling Many Optional Parameters): constructor එකේ parameters ගොඩක් තිබුණත්, අපිට අවශ්‍ය ඒවා විතරක් set කරන්න පුළුවන්. default values දෙන්න පුළුවන් නිසා null නැත්නම් 0 වගේ දේවල් දාන්න ඕන වෙන්නේ නැහැ.
  • Object Creation Logic එක වෙන් කිරීම (Separation of Concerns): Object එකේ business logic එකයි, ඒක හදන logic එකයි වෙන් වෙනවා. Product class එකේ constructor එක සරල වෙනවා.
  • Immutable Objects හැදීම (Creating Immutable Objects): Object එකක් හැදුවට පස්සේ ඒක වෙනස් කරන්න බැරි වෙන්න (immutable) හදන්න Builder Pattern එක ගොඩක් ප්‍රයෝජනවත්. Product class එකේ fields ටික final කරලා, setters නොදී, Builder එකෙන් විතරක් Object එක create කරන්න පුළුවන්.
  • Validation: Object එක හදන්න කලින්, Builder එකේ build() method එක ඇතුලේ validation කරන්න පුළුවන්. උදාහරණයක් විදිහට RAM එක 4GB ට අඩුවෙන් දාන්න බැරි වෙන්න හදනවා වගේ.

කවදද Builder Pattern එක පාවිච්චි නොකරන්නේ?

හැම වෙලේම Builder Pattern එක හොඳම විසඳුම නෙවෙයි. Object එකක් හදන එක සරලයි නම් (parameters එකක් දෙකක් වගේ නම්), Builder Pattern එක overkill වෙන්න පුළුවන්. ඒකෙන් code එකේ අනවශ්‍ය විදිහට complexity එක වැඩි වෙන්න පුළුවන්. ඒ නිසා, මේ pattern එක complex objects හදන වෙලාවට විතරක් use කරන්න පුරුදු වෙන්න.

අවසන් වචන

Builder Design Pattern එක complex objects හදන එක සරල කරන, අපේ code එක clean කරන, maintain කරන්න ලේසි කරන powerful tool එකක්. විශේෂයෙන්ම domain objects වල attributes ගොඩක් තියෙනකොට, ඒ වගේම optional attributes තියෙනකොට මේ pattern එක මාරටම ප්‍රයෝජනවත්.

ඉතින් යාලුවනේ, ඔයාලට මේ concept එක තේරුණා කියලා මම හිතනවා. පුළුවන් නම්, මේ code එක ඔයාලගේ IDE එකට දාලා, run කරලා බලන්න. properties වෙනස් කරලා, new Laptop instances create කරලා අත්හදා බලන්න. ඒක තමයි හොඳම විදිහ, මේක හොඳට තේරුම් ගන්න. ඔයාලගේ අදහස්, ප්‍රශ්න, නැත්නම් මේ pattern එක පාවිච්චි කරපු අත්දැකීම් පහලින් comment එකක් විදිහට දාගෙන යන්න අමතක කරන්න එපා. තවත් අලුත් Design Pattern එකක් එක්ක හම්බවෙමු! තෙරුවන් සරණයි!