Java Builder Pattern: සංකීර්ණ Objects පහසුවෙන් හදමු | SC Guide

Java Builder Pattern: සංකීර්ණ Objects පහසුවෙන් හදමු | SC Guide

Builder Pattern එකෙන් සංකීර්ණ Java Objects පහසුවෙන් හදමු! (Mastering Complex Java Objects with Builder Pattern)

කෝ බලන්න, ඔයාලා Java වලදී object එකක් හදනකොට කී පාරක්ද constructor එකට පරාමීටර (parameters) ගොඩක් දාන්න වෙලා තියෙන්නේ? සමහර වෙලාවට fields 10-15ක් වගේ තියෙන object එකක් හදනකොට, constructor එක මෙහෙම පේන්න පුළුවන්:


// අම්මෝ! මේක කියවන්නත් අමාරුයි නේද?
User user = new User("John", "Doe", "[email protected]", "+94712345678", "123 Main St, Colombo", 30, true, "Software Engineer", Arrays.asList("Java", "Spring"));

මේකට කියන්නේ "Constructor Parameter Teldoscopy" කියලා. එකපාරටම මෙච්චර parameter දාද්දී මොකද වෙන්නේ කියලා තේරුම් ගන්න අමාරුයි. ඒ වගේම, මේකේ සමහර parameter optional වෙන්න පුළුවන්. ඒ කියන්නේ හැම වෙලාවෙම ඒ හැම parameter එකක්ම දාන්න ඕනේ නෑ. එතකොට, optional parameter වලට null දදා යන්න වෙනවා. ඒක පට්ට අවුල් වැඩක් නේද?

මේ වගේ ප්‍රශ්න වලට තියෙන පට්ටම විසඳුමක් තමයි "Builder Design Pattern" එක. මේක creational design patterns ගොඩට අයිති වෙන එකක්. මේ article එකෙන් අපි Builder Pattern එක මොකද්ද, ඒක කොහොමද implement කරන්නේ, ඒකේ වාසි අවාසි මොනවද, සහ කවදාද මේක පාවිච්චි කරන්න ඕනේ කියලා පියවරෙන් පියවර බලමු. ඒ වගේම Sri Lankan accent එකට ගැලපෙන්න අපි කතා කරන විදිහට මේක විස්තර කරන්නම්. එහෙනම් වැඩේට බහිමු!

Builder Pattern එක මොකද්ද? (What is the Builder Pattern?)

සරලවම කිව්වොත්, Builder Pattern එක නිර්මාණය කරලා තියෙන්නේ සංකීර්ණ object එකක් කොටස් වශයෙන් හදන්න. ඒ කියන්නේ, object එකක් හදන විදිහයි (construction) ඒකේ අවසාන හැඩයයි (representation) වෙන් කරලා තියෙන එක. මේකෙන් වෙන්නේ, එකම construction process එකකින් විවිධ representations (final objects) හදන්න පුළුවන් වෙන එක.

හිතන්න ඔයාලා ගෙයක් හදනවා කියලා. සාමාන්‍යයෙන් ගෙයක් හදන්න කටයුතු ගොඩක් තියෙනවා: foundation එක දානවා, බිත්ති ටික හදනවා, වහලය හදනවා, පාට කරනවා, විදුලිය දානවා වගේ ගොඩක් දේවල්. මේ හැම දේම එක පාරටම කරන්න බෑනේ. පියවරෙන් පියවර කරන්න ඕනේ. Builder Pattern එකත් හරියට එහෙමයි. අපේ object එකේ තියෙන attributes ටික ටික set කරලා, අන්තිමට ඒ හැම attribute එකක්ම එකතු කරලා final object එක හදනවා.

මේකේ ප්‍රධාන වාසිය තමයි, object එකක් හදන process එක ගොඩක් කියවන්න පහසු වෙන එක. ඒ වගේම, object එකට තියෙන optional fields ගොඩක් ලේසියෙන් handle කරන්න පුළුවන්. ඒ විතරක් නෙවෙයි, මේක පාවිච්චි කරලා immutable objects හදන්නත් පුළුවන්.

අපිම Builder Pattern එකක් හදමු! (Let's build a Builder Pattern ourselves!)

හරි, දැන් අපි උඩින් දැක්කා වගේ User object එකක් Builder Pattern එකෙන් කොහොමද හදන්නේ කියලා බලමු. මේ User object එකට fields ගොඩක් තියෙනවා, සමහර ඒවා mandatory (අනිවාර්යයි), සමහර ඒවා optional.

සාමාන්‍ය constructor එකකින් තියෙන ප්‍රශ්නය (The problem with traditional constructors)

සාමාන්‍යයෙන් අපි User object එක හදන්නේ මෙහෙමයි. මේකේ fields firstName, lastName, email, phone, address, age, isVerified, occupation, skills කියලා ගමු.


public class User {
    private String firstName;
    private String lastName;
    private String email;
    private String phone;
    private String address;
    private int age;
    private boolean isVerified;
    private String occupation;
    private List<String> skills;

    // Telescoping constructor - මේක නම් පට්ට අවුල්!
    public User(String firstName, String lastName, String email) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
    }

    public User(String firstName, String lastName, String email, String phone) {
        this(firstName, lastName, email);
        this.phone = phone;
    }

    public User(String firstName, String lastName, String email, String phone, String address) {
        this(firstName, lastName, email, phone);
        this.address = address;
    }
    // මේ වගේ ගොඩක් constructor ලියන්න වෙනවා parameter combinations ගණන අනුව!
    // අමතක කරන්න එපා getters විතරක් දාන්න. Setters නෑ object එක immutable කරන්න.
    // Getters
    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public String getEmail() { return email; }
    public String getPhone() { return phone; }
    public String getAddress() { return address; }
    public int getAge() { return age; }
    public boolean isVerified() { return isVerified; }
    public String getOccupation() { return occupation; }
    public List<String> getSkills() { return skills; }
}

දැක්කා නේද? parameter ටිකක් වෙනස් වෙද්දි අලුතෙන් constructor එකක් ලියන්න වෙනවා. මේක නඩත්තු කරන්නත් අමාරුයි, කියවන්නත් අමාරුයි.

Builder Pattern එක implement කරමු! (Let's implement the Builder Pattern!)

අපි මේක කරන්නේ User class එක ඇතුලෙම UserBuilder කියලා static nested class එකක් හදලා. මේ UserBuilder class එක තමයි අපේ User object එක හදන්න උදව් කරන්නේ.


import java.util.List;
import java.util.ArrayList; // ArrayList එකක් විදිහට initialize කරන්න ඕනේ.

public class User {
    private final String firstName; // mandatory
    private final String lastName;  // mandatory
    private final String email;     // mandatory
    private final String phone;     // optional
    private final String address;   // optional
    private final int age;          // optional
    private final boolean isVerified; // optional
    private final String occupation; // optional
    private final List<String> skills; // optional

    // private constructor එකක් දාන්නේ, object එක හදන්න පුළුවන් Builder එකට විතරක් වෙන්න.
    // මේකෙන් අපේ User object එක Immutable වෙනවා. හැදුවට පස්සේ වෙනස් කරන්න බෑ.
    private User(UserBuilder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.email = builder.email;
        this.phone = builder.phone;
        this.address = builder.address;
        this.age = builder.age;
        this.isVerified = builder.isVerified;
        this.occupation = builder.occupation;
        this.skills = builder.skills;
    }

    // Getters for all fields
    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public String getEmail() { return email; }
    public String getPhone() { return phone; }
    public String getAddress() { return address; }
    public int getAge() { return age; }
    public boolean isVerified() { return isVerified; }
    public String getOccupation() { return occupation; }
    public List<String> getSkills() { return skills; }

    // UserBuilder inner static class එක
    public static class UserBuilder {
        private String firstName;
        private String lastName;
        private String email;
        private String phone;
        private String address;
        private int age = 0; // default value for optional int
        private boolean isVerified = false; // default value for optional boolean
        private String occupation = "N/A"; // default value for optional String
        private List<String> skills = new ArrayList<>(); // default value for optional List

        // Mandatory fields constructor එකටම දාන්න පුළුවන්, නැත්නම් Builder methods වලින් ගන්නත් පුළුවන්.
        // අපි මේකේ mandatory fields constructor එකටම දාමු.
        public UserBuilder(String firstName, String lastName, String email) {
            // Null checks හෝ validation මෙතන කරන්න පුළුවන්
            if (firstName == null || firstName.isEmpty() || lastName == null || lastName.isEmpty() || email == null || email.isEmpty()) {
                throw new IllegalArgumentException("First name, last name, and email are mandatory.");
            }
            this.firstName = firstName;
            this.lastName = lastName;
            this.email = email;
        }

        public UserBuilder withPhone(String phone) {
            this.phone = phone;
            return this; // chaining කරන්න return this අනිවාර්යයි
        }

        public UserBuilder withAddress(String address) {
            this.address = address;
            return this;
        }

        public UserBuilder withAge(int age) {
            if (age < 0) throw new IllegalArgumentException("Age cannot be negative.");
            this.age = age;
            return this;
        }

        public UserBuilder withVerified(boolean isVerified) {
            this.isVerified = isVerified;
            return this;
        }

        public UserBuilder withOccupation(String occupation) {
            this.occupation = occupation;
            return this;
        }

        public UserBuilder withSkills(List<String> skills) {
            this.skills = skills;
            return this;
        }

        public User build() {
            // අවශ්‍ය නම් තවත් validation මෙතන කරන්න පුළුවන්
            return new User(this); // User object එක හදලා return කරනවා
        }
    }

    // Optional: toString() method for easy printing
    @Override
    public String toString() {
        return "User{" +
               "firstName='" + firstName + '\'' +
               ", lastName='" + lastName + '\'' +
               ", email='" + email + '\'' +
               ", phone='" + phone + '\'' +
               ", address='" + address + '\'' +
               ", age=" + age +
               ", isVerified=" + isVerified +
               ", occupation='" + occupation + '\'' +
               ", skills=" + skills +
               '}';
    }
}

දැන් අපි බලමු මේ UserBuilder එක පාවිච්චි කරලා User object එකක් හදන්නේ කොහොමද කියලා. මේක තමයි වැඩේ එල!


import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        // අනිවාර්ය fields විතරක් දීලා User object එකක් හදනවා
        User user1 = new User.UserBuilder("Kamal", "Perera", "[email protected]").build();
        System.out.println("User 1: " + user1);
        // Output: User{firstName='Kamal', lastName='Perera', email='[email protected]', phone='null', address='null', age=0, isVerified=false, occupation='N/A', skills=[]}

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

        // සමහර optional fields එක්ක User object එකක් හදනවා
        User user2 = new User.UserBuilder("Nimal", "Silva", "[email protected]")
                            .withPhone("+94771234567")
                            .withAge(28)
                            .withOccupation("DevOps Engineer")
                            .build();
        System.out.println("User 2: " + user2);
        // Output: User{firstName='Nimal', lastName='Silva', email='[email protected]', phone='+94771234567', address='null', age=28, isVerified=false, occupation='DevOps Engineer', skills=[]}

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

        // හැම field එකක්ම දීලා User object එකක් හදනවා
        User user3 = new User.UserBuilder("Sara", "Fernando", "[email protected]")
                            .withPhone("+94709876543")
                            .withAddress("45 Galle Road, Mount Lavinia")
                            .withAge(35)
                            .withVerified(true)
                            .withOccupation("Software Architect")
                            .withSkills(Arrays.asList("Java", "Spring Boot", "AWS", "Microservices"))
                            .build();
        System.out.println("User 3: " + user3);
        // Output: User{firstName='Sara', lastName='Fernando', email='[email protected]', phone='+94709876543', address='45 Galle Road, Mount Lavinia', age=35, isVerified=true, occupation='Software Architect', skills=[Java, Spring Boot, AWS, Microservices]}

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

        // Mandatory fields නැතිව object එකක් හදන්න හැදුවොත් error එකක් එනවා
        try {
            User user4 = new User.UserBuilder(null, "Bandara", "[email protected]").build();
            System.out.println("User 4: " + user4);
        } catch (IllegalArgumentException e) {
            System.out.println("Error creating user 4: " + e.getMessage());
        }
        // Output: Error creating user 4: First name, last name, and email are mandatory.
    }
}

දැන් බලන්න, User object එක හදන එක කොච්චර පැහැදිලිද කියලා. ඕන කරන fields ටික විතරක් දාලා, chaining කරගෙන යන්න පුළුවන්. ඒක කියවන්නත් ලේසියි, නඩත්තු කරන්නත් ලේසියි.

Builder Pattern එකේ වාසි සහ අවාසි (Pros and Cons of the Builder Pattern)

වාසි (Pros):

  • කියවීමේ පහසුව (Readability and Maintainability): Object එක හදන process එක ගොඩක් පැහැදිලියි. මොන parameter එකටද මොන value එක යන්නේ කියලා ලේසියෙන් හඳුනාගන්න පුළුවන්.
  • Optional parameters ලේසියෙන් handle කිරීම (Handles optional parameters gracefully): Object එකට තියෙන optional fields ගොඩක් තිබුණත්, ඒ හැම එකකටම value එකක් දාන්න අවශ්‍ය වෙන්නේ නෑ. ඕන කරන ඒවා විතරක් set කරන්න පුළුවන්.
  • Immutable objects නිර්මාණයට උදව් වීම (Facilitates immutable object creation): Builder Pattern එකෙන් හදන object එකේ constructor එක private කරලා, setters නැති කරලා immutable කරන්න පුළුවන්. ඒ කියන්නේ, object එක හැදුවට පස්සේ ඒකේ values වෙනස් කරන්න බෑ. මේක thread-safe operations වලදී ගොඩක් වැදගත්.
  • Step-by-step construction: Object එකක් ටිකෙන් ටික, පියවරෙන් පියවර හදන්න පුළුවන්.
  • Constructor clutter අඩු කරයි (Reduces constructor clutter): Telescoping constructors වගේ එකම class එකේ constructor ගොඩක් ලියන එක වළක්වනවා.

අවාසි (Cons):

  • වැඩිපුර code ලිවීමට සිදුවීම (More boilerplate code): Builder Pattern එක implement කරන්න අනිත් ක්‍රම වලට වඩා වැඩිපුර code ලියන්න වෙනවා. (හැබැයි මේකට Lombok වගේ libraries පාවිච්චි කරන්න පුළුවන්, ඒ ගැන පස්සේ කතා කරමු.)
  • සරල objects වලට අනවශ්‍ය වීම (Can be overkill for simple objects): Object එකට fields එකක් දෙකක් වගේ පොඩ්ඩක් තියෙනවා නම් Builder Pattern එක පාවිච්චි කරන එක තේරුමක් නෑ. ඒකට සාමාන්‍ය constructor එකක් ඇති.

කවදාද Builder Pattern එක පාවිච්චි කරන්නේ? (When to use the Builder Pattern?)

Builder Pattern එක පාවිච්චි කරන්න හොඳම අවස්ථා කිහිපයක් මෙන්න:

  • Object එකකට parameter ගොඩක් තියෙනකොට (Large number of parameters): විශේෂයෙන්ම, ඒ parameters වලින් ගොඩක් optional නම්.
  • Object construction එක සංකීර්ණ නම් (Complex object construction): Object එකක් හදන්න පියවර ගණනාවක් තියෙනවා නම්, නැත්නම් ඒකට විවිධ configurations තියෙනවා නම්.
  • Immutable objects අවශ්‍ය නම් (Need immutable objects): Object එකක් හැදුවට පස්සේ ඒකේ තත්ත්වය (state) වෙනස් වෙන්න දෙන්න බෑ නම්.
  • "Telescoping constructors" වළක්වා ගැනීමට (To avoid "telescoping constructors"): එකම object එකට විවිධ parameter sets වලට constructor ගොඩක් ලියන්න වෙනවා නම්.

Tips එකක් විදිහට, ඔයාලට මේ boilerplate code එක අඩු කරගන්න පුළුවන් Lombok library එකේ @Builder annotation එක පාවිච්චි කරලා. ඒකෙන් compile-time එකේදී auto-generate වෙනවා මේ Builder code එක. ඒ ගැන වෙනම article එකකින් කතා කරන්නම්.

නිගමනය (Conclusion)

Java වල සංකීර්ණ object හදන එක ලේසි කරන්න Builder Design Pattern එක කියන්නේ පට්ටම powerful tool එකක්. මේකෙන් අපේ code එක ගොඩක් කියවන්න පහසු වෙනවා, maintain කරන්න ලේසි වෙනවා, ඒ වගේම object එකේ robustness එක වැඩි වෙනවා.

අදම ඔයාලගේ project එකක පොඩි තැනකින් හරි මේක පාවිච්චි කරලා බලන්න. එතකොට තමා මේකේ වටිනාකම තේරෙන්නේ! මුලදී ටිකක් code ලියන්න වැඩි වුණාට, long-term එකේදී මේක ගොඩක් ප්‍රයෝජනවත් වෙනවා.

ඔයාලගේ අදහස් පහලින් comment කරන්න අමතක කරන්න එපා. මේ වගේ තවත් design patterns ගැන දැනගන්න කැමති නම් ඒකත් කියන්න! මේ article එක ඔයාලට වටිනවා කියලා හිතෙනවා නම් යාළුවොත් එක්ක share කරන්නත් පුළුවන්.

තවත් අලුත් article එකකින් හමුවෙමු, හැමෝටම ජය!