ෆැක්ටරි පැටර්න් සරලව - Object Creation Magic | SC Guide

ෆැක්ටරි පැටර්න් සරලව - Object Creation Magic | SC Guide

ආයුබෝවන් යාළුවනේ!

ඔන්න අද අපි කතා කරන්න යන්නේ software development වලදී ගොඩක් වැදගත් වෙන, ඒ වගේම ගොඩක් අයව ටිකක් විතර අවුලට පත් කරන්න පුලුවන් Design Pattern එකක් ගැන. ඒ තමයි Factory Pattern එක. මේක හරියට software එකක construction site එකේ material supply කරන warehouse එකක් වගේ. ඕන වෙලාවට, ඕන කරන type එකේ object එක හදලා දෙනවා. කලබල වෙන්න එපා! අපි මේක අද සරලව, ඔයාලට තේරෙන භාෂාවෙන්, අපේම විදිහට කතා කරමු.

Software development කියන්නේ ඉතින් ලොකු ගොඩනැගිල්ලක් හදනවා වගේ වැඩක්නේ. ගොඩනැගිල්ලක් හදද්දි bricks, cement, sand, iron rods වගේ දේවල් ඕන වෙනවා වගේම, software එකක් හදද්දිත් objects ඕන වෙනවා. මේ objects හදන විදිහ organize කරගන්න, maintain කරන්න, ඒ වගේම future changes වලට ලේසියෙන් adapt වෙන්න තමයි Design Patterns පාවිච්චි කරන්නේ. ඒ අතරින් Factory Pattern එක කියන්නේ object creation process එක customize කරන්න, simplify කරන්න, ඒ වගේම decouple කරන්න use කරන ක්‍රමයක්.

හිතන්නකෝ ඔයාලට mobile phone එකක් ඕන කියලා. ඒත් ඔයාලා phone එක හදන component parts එකින් එක assemble කරන්නේ නෑනේ. ඔයාලා කරන්නේ factory එකකින් හදපු phone එකක් ගන්න එක. Factory Pattern එකත් software development වලදී කරන්නේ ඒ වගේ වැඩක් තමයි. Object එකක් හදන්න ඕන වුණාම, ඒ object එක හදන logic එක client code එකෙන් වෙන් කරලා, ඒ වැඩේ factory එකකට භාර දෙනවා.

Design Patterns සහ Factory Pattern කියන්නේ මොනවද?

සරලවම Design Patterns කියන්නේ software development වලදී නිතරම එන පොදු ගැටළු වලට දීලා තියෙන හොඳම විසඳුම් (best practices) ටිකක්. මේවා programmers ලා පරම්පරා ගාණක් තිස්සේ හොයාගෙන, improve කරගෙන ආපු දේවල්. මේවා පාවිච්චි කරන එකෙන් අපේ code එක readable, maintainable, reusable, extensible විදිහට හදාගන්න පුළුවන්. ඒ වගේම team එකක් විදිහට වැඩ කරද්දි හැමෝටම තේරෙන standard practice එකක් හදාගන්නත් මේවා උදව් වෙනවා.

Factory Pattern එක අයත් වෙන්නේ Creational Design Patterns කියන වර්ගයට. ඒ කියන්නේ මේවා object creation mechanisms වලට සම්බන්ධයි. මේකේ ප්‍රධාන අරමුණ තමයි object එකක් හදන logic එක, ඒ object එක use කරන client code එකෙන් වෙන් කරන එක. මේකෙන් වෙන ලොකුම වාසිය තමයි, object එකක් හදන විදිහ වෙනස් වුණත්, client code එකේ කිසිම වෙනසක් කරන්න අවශ්‍ය වෙන්නේ නැති එක.

උදාහරණයක් විදිහට අපි හිතමු ඔයාලා online food ordering system එකක් හදනවා කියලා. ඒ system එකේ pizza, burger, pasta වගේ foods තියෙනවා. මේ හැම food item එකක්ම object එකක් විදිහට හදන්න ඕන. දැන් හිතන්න, අනාගතයේදී ඔයාලා new food type එකක් add කරනවා, නැත්නම් existing food type එකක preparation process එක වෙනස් කරනවා කියලා. Factory Pattern එකක් නැතිව මේක කරොත්, food order කරන හැම තැනකම ඒ new logic එක add කරන්න, modify කරන්න වෙනවා. ඒත් factory එකක් පාවිච්චි කළොත්, වෙනස්කම් කරන්න වෙන්නේ factory එකේ විතරයි. එතකොට අනිත් code එකට කිසිම බලපෑමක් වෙන්නේ නෑ.

Factory Pattern එකේ වාසි මොනවද?

  • Decoupling (වෙන් කිරීම): Object එකක් හදන logic එක, ඒ object එක use කරන client code එකෙන් සම්පූර්ණයෙන්ම වෙන් කරනවා. මේකෙන් code එකේ dependency අඩු වෙනවා.
  • Flexibility (නම්‍යශීලී බව): අනාගතයේදී අලුත් object types add කරන්න හෝ existing ones modify කරන්න ලේසියි. Client code එකේ වෙනසක් කරන්න අවශ්‍ය වෙන්නේ නෑ.
  • Maintainability (නඩත්තු කිරීමේ පහසුව): Object creation logic එක එකම තැනක තියෙන නිසා, changes කරන්න හෝ bugs fix කරන්න පහසුයි.
  • Encapsulation: Object creation process එකේ සංකීර්ණතා client code එකෙන් සඟවනවා. Client එක දන්නේ factory එකෙන් object එකක් ඉල්ලන විදිහ විතරයි.
  • Testability: Object creation logic එක isolate කරන්න පුළුවන් නිසා, ඒ කොටස වෙනම test කරන්න පහසුයි.

සරල Factory Pattern Implementation එකක් (Java උදාහරණයක්)

අපි දැන් සරල Java code example එකකින් Factory Pattern එක කොහොමද වැඩ කරන්නේ කියලා බලමු. අපි notification system එකක් හදනවා කියලා හිතමු. ඒකේ Email, SMS, Push වගේ notifications වර්ග තියෙනවා. අපි මේ notifications හදන්න Factory Pattern එකක් පාවිච්චි කරමු.

1. Notification Interface එක

// Notification.java
public interface Notification {
    void notifyUser();
}

2. Concrete Notification Classes

// EmailNotification.java
public class EmailNotification implements Notification {
    @Override
    public void notifyUser() {
        System.out.println("Sending an Email notification.");
    }
}

// SMSNotification.java
public class SMSNotification implements Notification {
    @Override
    public void notifyUser() {
        System.out.println("Sending an SMS notification.");
    }
}

// PushNotification.java
public class PushNotification implements Notification {
    @Override
    public void notifyUser() {
        System.out.println("Sending a Push notification.");
    }
}

3. NotificationFactory Class එක

// NotificationFactory.java
public class NotificationFactory {
    public Notification createNotification(String type) {
        if (type == null || type.isEmpty()) {
            return null;
        }
        switch (type.toLowerCase()) {
            case "email":
                return new EmailNotification();
            case "sms":
                return new SMSNotification();
            case "push":
                return new PushNotification();
            default:
                throw new IllegalArgumentException("Unknown notification type: " + type);
        }
    }
}

4. Client Code එක

// Main.java
public class Main {
    public static void main(String[] args) {
        NotificationFactory factory = new NotificationFactory();

        // Email notification request
        Notification emailNotif = factory.createNotification("email");
        if (emailNotif != null) {
            emailNotif.notifyUser();
        }

        // SMS notification request
        Notification smsNotif = factory.createNotification("sms");
        if (smsNotif != null) {
            smsNotif.notifyUser();
        }

        // Push notification request
        Notification pushNotif = factory.createNotification("push");
        if (pushNotif != null) {
            pushNotif.notifyUser();
        }

        // Invalid notification type
        try {
            Notification invalidNotif = factory.createNotification("telegram");
        } catch (IllegalArgumentException e) {
            System.err.println(e.getMessage());
        }
    }
}

මේ උදාහරණයේදී NotificationFactory එක තමයි Notification objects හදන්නේ. Client code එකට (Main class එකට) Notification object එකක් හදන විදිහ ගැන කිසිම දෙයක් දැනගන්න අවශ්‍ය වෙන්නේ නෑ. එයාලා දන්නේ factory එකෙන් තමන්ට ඕන type එකේ notification එක ඉල්ලන විදිහ විතරයි. මේක තමයි decoupling කියන්නේ.

Spring Framework එකේ Factory Bean කියන්නේ මොකද්ද?

අපි දැන් Factory Pattern එක ගැන කතා කළානේ. Spring Framework එකත් මේ Design Patterns ගොඩක් use කරන framework එකක්. Spring එකේ තියෙන Factory Pattern එකේම විශේෂිත implementation එකක් තමයි FactoryBean කියන්නේ. මේක Java වල තියෙන `org.springframework.beans.factory.FactoryBean` interface එක implement කිරීමෙන් හදාගන්න පුළුවන්.

සාමාන්‍යයෙන් Spring IoC (Inversion of Control) container එකේදී, අපි Bean definition එකක් (Java class එකක්) specify කළාම, container එක ඒ class එකේ instance එකක් හදලා අපිට දෙනවා. ඒත් සමහර වෙලාවට අපිට bean එකක් හදන්න ටිකක් සංකීර්ණ logic එකක් ඕන වෙනවා. නැත්නම්, අපිට class එකක් customize කරලා, ඒකේ instance එකක් spring container එකට දෙන්න ඕන වෙනවා.

හරියටම කියනවා නම්, `FactoryBean` එක කියන්නේ Spring IoC container එක ඇතුලේදී complex object creation logic එකක් implement කරන්න දීලා තියෙන යාන්ත්‍රණයක්. `FactoryBean` එකක් configure කළාම, Spring container එක ඒ `FactoryBean` එකේ instance එකක් හදනවා. ඒත් අපි ඇත්තටම `getBean()` කියලා ඉල්ලුවම අපිට ලැබෙන්නේ `FactoryBean` එකෙන් return කරන object එක මිසක්, `FactoryBean` class එකේ instance එක නෙවෙයි. මේක ගොඩක් වැදගත් concept එකක්.

Spring Factory Bean එකක් හදමු

උදාහරණයක් විදිහට අපි Database Connection (DataSource) එකක් හදමු. DataSources කියන්නේ complex objects. ඒවට connection pool settings, driver class names, URLs, credentials වගේ ගොඩක් දේවල් configure කරන්න වෙනවා. මේක FactoryBean එකක් හරහා කරන එකෙන් configuration එක manage කරන්න පහසුයි.

1. DataSourceFactoryBean class එක

import org.springframework.beans.factory.FactoryBean;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

public class DataSourceFactoryBean implements FactoryBean<DataSource> {

    private String driverClassName;
    private String url;
    private String username;
    private String password;

    // Setters for properties (Spring will inject these)
    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public DataSource getObject() throws Exception {
        // This is where the complex object creation logic goes
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setPassword(password);
        dataSource.setUsername(username);
        System.out.println("DataSource created by FactoryBean: " + url);
        return dataSource;
    }

    @Override
    public Class<?> getObjectType() {
        return DataSource.class;
    }

    @Override
    public boolean isSingleton() {
        return true; // We want a single instance of the DataSource
    }
}

2. Spring Configuration එක (XML or Java Config)

XML Configuration:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myDataSource" class="DataSourceFactoryBean">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mydatabase"/>
        <property name="username" value="root"/>
        <property name="password" value="password"/>
    </bean>

</beans>

Java Configuration:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;

@Configuration
public class AppConfig {

    @Bean
    public DataSourceFactoryBean myDataSource() {
        DataSourceFactoryBean factoryBean = new DataSourceFactoryBean();
        factoryBean.setDriverClassName("com.mysql.cj.jdbc.Driver");
        factoryBean.setUrl("jdbc:mysql://localhost:3306/mydatabase");
        factoryBean.setUsername("root");
        factoryBean.setPassword("password");
        return factoryBean;
    }

    // If you need to consume the DataSource bean
    @Bean
    public MyService myService(DataSource dataSource) {
        return new MyService(dataSource);
    }
}

3. Client Code එක (Main application)

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.sql.DataSource;

public class ClientApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // Or for Java Config: 
        // ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        DataSource dataSource = (DataSource) context.getBean("myDataSource");
        System.out.println("Received DataSource object: " + dataSource.getClass().getName());
        // You can now use the dataSource object for database operations

        // If you want the FactoryBean instance itself (rarely needed)
        // DataSourceFactoryBean factoryBeanInstance = (DataSourceFactoryBean) context.getBean("&myDataSource");
        // System.out.println("Received FactoryBean instance: " + factoryBeanInstance.getClass().getName());
    }
}

මේ උදාහරණයේදී, අපි `myDataSource` කියන bean එක ඉල්ලුවම Spring container එක අපිට දෙන්නේ `DataSourceFactoryBean` එකෙන් `getObject()` method එක හරහා හදලා දුන්නු `DriverManagerDataSource` object එකක්. මේක `FactoryBean` එකේ ලොකුම විශේෂත්වය. `&myDataSource` කියලා ඉල්ලුවොත් තමයි FactoryBean instance එකම ලැබෙන්නේ.

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

  • වර්ග කිහිපයක objects හදන්න ඕන නම්: එකම interface එකක් implement කරන, නමුත් විවිධ concrete implementations තියෙන objects හදන්න ඕන වෙලාවට. (උදා: අපේ Notification උදාහරණය වගේ).
  • Object creation process එක සංකීර්ණ නම්: Object එකක් හදන්න set of steps කිහිපයක් හෝ complex configuration එකක් ඕන වෙනවා නම්. (උදා: Spring DataSource FactoryBean එක වගේ).
  • Client code එක object creation logic එකෙන් වෙන් කරන්න ඕන නම්: Client code එකට තමන් use කරන object එක හදන විදිහ ගැන කිසිම දැනුමක් අවශ්‍ය නැති වෙලාවට.
  • System එක extend කරන්න පහසු කරන්න: අනාගතයේදී අලුත් product types add කරන්න ඕන වෙලාවට, existing code එකට බලපෑමක් නොවී ඒක කරන්න පුළුවන්.

අවසාන වශයෙන්

Factory Pattern එක කියන්නේ object-oriented programming වලදී object creation process එක effectively manage කරන්න දීලා තියෙන powerful tool එකක්. මේක පාවිච්චි කරන එකෙන් අපේ code base එක clean, maintainable, extensible විදිහට හදාගන්න පුළුවන්. Spring Framework එකේ FactoryBean එකත් මේ Design Pattern එකේම practical application එකක්. මේ concepts හොඳට තේරුම් ගන්න පුළුවන් නම්, ඔයාලට වඩාත් robust, flexible applications හදන්න පුළුවන්.

ඉතින්, මේ post එක කියවලා ඔයාලට Factory Pattern එක සහ Spring Factory Bean ගැන හොඳ අවබෝධයක් ලැබෙන්න ඇති කියලා හිතනවා. මතක තියාගන්න, Design Patterns කියන්නේ පාඩම් කරන්න ඕන දේවල් නෙවෙයි, තේරුම් අරන් ප්‍රයෝගිකව යොදාගන්න ඕන දේවල්. මේ concepts ඔයාලගේ project වලට දාලා බලන්න! මොකද, Practice makes perfect නේ!

මේ ගැන ඔයාලගේ අදහස් මොනවද? නැත්නම් මේක ගැන තව මොනවද දැනගන්න ඕන කියලා comment section එකේ කියන් යන්න. අපි ඊළඟ post එකෙන් තවත් වැදගත් topic එකකින් හමුවෙමු!