DESIGN-PATTERNS SOFTWARE-ARCHITECTURE CLEAN-CODE OBJECT-ORIENTED-PROGRAMMING JAVASCRIPT TYPESCRIPT BACKEND-DEVELOPMENT FRONTEND-DEVELOPMENT FLEXIBILITY MAINTAINABILITY TESTABILITY

Membangun Kode yang Fleksibel dan Mudah Diubah dengan Factory Pattern: Panduan Praktis untuk Developer Web

⏱️ 11 menit baca
👨‍💻

Membangun Kode yang Fleksibel dan Mudah Diubah dengan Factory Pattern: Panduan Praktis untuk Developer Web

1. Pendahuluan

Pernahkah Anda merasa kode Anda semakin sulit dikelola ketika harus membuat banyak objek dari kelas yang berbeda, namun memiliki antarmuka (interface) yang serupa? Misalnya, Anda punya berbagai jenis koneksi database, atau berbagai jenis notifikasi (email, SMS, push notification), dan logika untuk membuat objek-objek ini mulai berserakan di mana-mana dalam kode Anda.

Masalah ini umum terjadi dan seringkali mengarah pada kode yang kaku, sulit diuji, dan rentan terhadap perubahan. Setiap kali ada jenis objek baru, Anda harus memodifikasi banyak bagian kode. 😩

Di sinilah Factory Pattern datang sebagai pahlawan! Sebagai salah satu dari Creational Design Patterns, Factory Pattern menyediakan cara untuk membuat objek tanpa harus mengekspos logika pembuatan objek tersebut ke klien (kode yang menggunakan objek). Ini adalah fondasi penting untuk membangun aplikasi web yang fleksibel, mudah diuji, dan skalabel, baik di sisi backend maupun frontend.

Dalam artikel ini, kita akan menyelami Factory Pattern, memahami cara kerjanya, manfaatnya, dan bagaimana mengimplementasikannya dengan contoh-contoh konkret menggunakan JavaScript dan TypeScript yang relevan untuk developer web. Mari kita mulai! 🚀

2. Apa Itu Factory Pattern?

📌 Definisi Sederhana: Factory Pattern adalah pola desain di mana kita menggunakan sebuah “pabrik” (factory) untuk membuat objek tanpa harus menentukan secara eksplisit kelas objek yang akan dibuat. Pabrik ini akan bertanggung jawab untuk memutuskan objek mana yang harus dibuat berdasarkan parameter atau kondisi tertentu.

💡 Analogi Pabrik Mobil: Bayangkan Anda ingin membeli mobil. Anda tidak perlu tahu detail rumit tentang bagaimana setiap bagian mobil dibuat, dirakit, atau dicat. Anda hanya pergi ke dealer, memilih jenis mobil (sedan, SUV, sport), dan dealer (sebagai “factory”) akan menyerahkan mobil yang sudah jadi kepada Anda. Dealer tersebut memiliki logika internal untuk menghubungi pabrik yang tepat, dengan spesifikasi yang Anda inginkan.

Dalam konteks pemrograman:

Tujuan utama dari Factory Pattern adalah untuk mengisolasi logika pembuatan objek dari kode yang menggunakannya. Ini berarti klien tidak perlu tahu detail implementasi dari objek yang dibuat, hanya perlu tahu bagaimana meminta objek dari factory.

3. Mengapa Menggunakan Factory Pattern?

Mengapa kita harus repot-repot menambahkan lapisan abstraksi ini? Berikut adalah beberapa manfaat utama:

4. Implementasi Dasar Factory Pattern di JavaScript/TypeScript

Mari kita lihat perbandingan sederhana tanpa dan dengan Factory Pattern.

Tanpa Factory Pattern (Logika Pembuatan Objek Tersebar)

Misalkan Anda memiliki beberapa jenis notifikasi (email, SMS).

// notifier.ts
interface Notifier {
  send(message: string): void;
}

class EmailNotifier implements Notifier {
  send(message: string): void {
    console.log(`Sending email: ${message}`);
  }
}

class SMSNotifier implements Notifier {
  send(message: string): void {
    console.log(`Sending SMS: ${message}`);
  }
}

// app.ts (kode klien)
function processNotification(type: string, msg: string) {
  let notifier: Notifier;

  if (type === 'email') {
    notifier = new EmailNotifier();
  } else if (type === 'sms') {
    notifier = new SMSNotifier();
  } else {
    throw new Error('Unsupported notifier type');
  }

  notifier.send(msg);
}

processNotification('email', 'Halo, ini email dari aplikasi.');
processNotification('sms', 'Anda mendapatkan SMS baru.');
// processNotification('push', 'Notifikasi push baru'); // Ini akan error

Masalahnya: Jika Anda ingin menambahkan PushNotifier, Anda harus mengubah fungsi processNotification. Bayangkan jika processNotification ini ada di banyak tempat! 😱

Dengan Factory Pattern (Logika Pembuatan Objek Terpusat)

Sekarang, mari kita refactor menggunakan Factory Pattern.

// notifier.ts
interface Notifier {
  send(message: string): void;
}

class EmailNotifier implements Notifier {
  send(message: string): void {
    console.log(`Sending email: ${message}`);
  }
}

class SMSNotifier implements Notifier {
  send(message: string): void {
    console.log(`Sending SMS: ${message}`);
  }
}

class PushNotifier implements Notifier {
  send(message: string): void {
    console.log(`Sending push notification: ${message}`);
  }
}

// notifierFactory.ts
class NotifierFactory {
  static createNotifier(type: string): Notifier {
    switch (type) {
      case 'email':
        return new EmailNotifier();
      case 'sms':
        return new SMSNotifier();
      case 'push':
        return new PushNotifier(); // Tambahkan jenis baru di sini
      default:
        throw new Error(`Unsupported notifier type: ${type}`);
    }
  }
}

// app.ts (kode klien)
function processNotification(type: string, msg: string) {
  const notifier = NotifierFactory.createNotifier(type); // Klien hanya berinteraksi dengan factory
  notifier.send(msg);
}

processNotification('email', 'Halo, ini email dari aplikasi.');
processNotification('sms', 'Anda mendapatkan SMS baru.');
processNotification('push', 'Notifikasi push baru!'); // Sekarang berfungsi

Lihat perbedaannya? Ketika Anda menambahkan PushNotifier, Anda hanya perlu memodifikasi NotifierFactory.createNotifier. Fungsi processNotification (klien) tidak perlu diubah sama sekali! Ini adalah kekuatan decoupling. 💪

5. Studi Kasus Praktis untuk Developer Web

Factory Pattern sangat berguna dalam berbagai skenario di pengembangan web.

🎯 Studi Kasus 1: Factory untuk Koneksi Database (Backend)

Di backend, Anda mungkin berinteraksi dengan berbagai jenis database (MySQL, PostgreSQL, MongoDB, dsb.). Factory dapat mengabstraksi pembuatan objek koneksi database.

// database.ts
interface DatabaseConnection {
  connect(): Promise<void>;
  query(sql: string): Promise<any[]>;
  disconnect(): Promise<void>;
}

class MySQLConnection implements DatabaseConnection {
  constructor(private config: any) {}
  async connect() { console.log(`Connecting to MySQL with ${this.config.host}`); }
  async query(sql: string) { console.log(`MySQL Query: ${sql}`); return [{ id: 1, data: 'mysql' }]; }
  async disconnect() { console.log('Disconnecting from MySQL'); }
}

class PostgreSQLConnection implements DatabaseConnection {
  constructor(private config: any) {}
  async connect() { console.log(`Connecting to PostgreSQL with ${this.config.host}`); }
  async query(sql: string) { console.log(`PostgreSQL Query: ${sql}`); return [{ id: 2, data: 'postgres' }]; }
  async disconnect() { console.log('Disconnecting from PostgreSQL'); }
}

// databaseFactory.ts
type DbType = 'mysql' | 'postgresql' | 'mongodb'; // Contoh, bisa diperluas

class DatabaseConnectionFactory {
  static createConnection(type: DbType, config: any): DatabaseConnection {
    switch (type) {
      case 'mysql':
        return new MySQLConnection(config);
      case 'postgresql':
        return new PostgreSQLConnection(config);
      // case 'mongodb': return new MongoDBConnection(config); // Mudah ditambahkan
      default:
        throw new Error(`Unsupported database type: ${type}`);
    }
  }
}

// server.ts (kode backend)
async function startServer(dbType: DbType) {
  const dbConfig = { host: 'localhost', port: 5432, user: 'admin' };
  const db = DatabaseConnectionFactory.createConnection(dbType, dbConfig);

  await db.connect();
  const result = await db.query('SELECT * FROM users');
  console.log('Query Result:', result);
  await db.disconnect();
}

startServer('postgresql');
// startServer('mysql'); // Bisa dengan mudah ganti jenis DB

Dengan DatabaseConnectionFactory, Anda bisa dengan mudah mengganti jenis database yang digunakan hanya dengan mengubah parameter di factory, tanpa menyentuh logika bisnis yang menggunakan koneksi database. Ini sangat membantu dalam migrasi database atau mendukung multi-database.

🎯 Studi Kasus 2: Factory untuk Komponen UI (Frontend)

Di frontend, terutama di aplikasi React, Vue, atau Angular, Factory Pattern bisa digunakan untuk membuat komponen UI yang berbeda berdasarkan prop atau state.

// uiComponents.tsx (React example)
interface ButtonProps {
  label: string;
  onClick: () => void;
  variant: 'primary' | 'secondary' | 'danger';
}

const PrimaryButton: React.FC<ButtonProps> = ({ label, onClick }) => (
  <button style={{ backgroundColor: 'blue', color: 'white', padding: '10px' }} onClick={onClick}>
    {label}
  </button>
);

const SecondaryButton: React.FC<ButtonProps> = ({ label, onClick }) => (
  <button style={{ backgroundColor: 'gray', color: 'white', padding: '10px' }} onClick={onClick}>
    {label}
  </button>
);

const DangerButton: React.FC<ButtonProps> = ({ label, onClick }) => (
  <button style={{ backgroundColor: 'red', color: 'white', padding: '10px' }} onClick={onClick}>
    {label}
  </button>
);

// buttonFactory.ts
import React from 'react';

type ButtonComponent = React.FC<Omit<ButtonProps, 'variant'>>; // Komponen tanpa prop variant

class ButtonFactory {
  static createButton(variant: ButtonProps['variant']): ButtonComponent {
    switch (variant) {
      case 'primary':
        return PrimaryButton;
      case 'secondary':
        return SecondaryButton;
      case 'danger':
        return DangerButton;
      default:
        throw new Error(`Unsupported button variant: ${variant}`);
    }
  }
}

// App.tsx (kode klien)
import React from 'react';

const App: React.FC = () => {
  const MyPrimaryButton = ButtonFactory.createButton('primary');
  const MySecondaryButton = ButtonFactory.createButton('secondary');
  const MyDangerButton = ButtonFactory.createButton('danger');

  return (
    <div>
      <MyPrimaryButton label="Submit" onClick={() => alert('Primary clicked!')} />
      <MySecondaryButton label="Cancel" onClick={() => alert('Secondary clicked!')} />
      <MyDangerButton label="Delete" onClick={() => alert('Danger clicked!')} />
    </div>
  );
};

export default App;

Dalam contoh ini, ButtonFactory mengembalikan komponen React yang berbeda berdasarkan variant yang diminta. Ini memungkinkan Anda untuk menjaga logika pemilihan komponen tetap bersih dan terpusat, terutama jika ada banyak variasi atau logika kompleks untuk menentukan komponen mana yang harus dirender.

6. Varian Factory Pattern: Simple Factory vs. Factory Method

Ada dua varian utama dari Factory Pattern:

  1. Simple Factory (atau Static Factory Method): Ini adalah implementasi yang kita gunakan di semua contoh di atas. Sebuah kelas tunggal (atau fungsi) memiliki metode statis yang bertanggung jawab untuk membuat objek. Ini adalah yang paling umum dan seringkali cukup untuk banyak kasus.

    • Kapan digunakan? Ketika Anda memiliki jumlah produk yang relatif kecil dan stabil, dan logika pembuatan objek tidak terlalu kompleks atau tidak perlu di-override oleh subkelas.
  2. Factory Method: Ini sedikit lebih kompleks. Bukan metode statis, melainkan sebuah metode yang didefinisikan dalam sebuah interface atau abstract class, dan implementasinya disediakan oleh subkelas. Ini memungkinkan subkelas untuk memutuskan objek mana yang akan dibuat.

    • Kapan digunakan? Ketika Anda memiliki hierarki kelas produk yang kompleks, dan Anda ingin memungkinkan subkelas untuk menentukan jenis objek konkret yang akan dibuat. Ini sering ditemukan di framework atau library yang perlu menyediakan titik kustomisasi untuk pembuatan objek.

Untuk developer web tingkat beginner-intermediate, Simple Factory adalah titik awal yang sangat baik dan akan mencakup sebagian besar kebutuhan Anda. Jangan terpaku pada kompleksitas Factory Method jika Simple Factory sudah cukup.

7. Tips dan Best Practices

Kesimpulan

Factory Pattern adalah alat yang ampuh dalam kotak perkakas setiap developer web untuk membangun aplikasi yang lebih fleksibel, maintainable, dan testable. Dengan mengabstraksi logika pembuatan objek ke dalam sebuah “pabrik”, kita dapat mengurangi coupling, menyederhanakan penambahan jenis objek baru, dan membuat kode kita lebih bersih secara keseluruhan.

Ingat, tujuan utama pola desain adalah untuk memecahkan masalah umum dan meningkatkan kualitas kode. Jangan ragu untuk mengidentifikasi area di aplikasi Anda di mana Factory Pattern bisa memberikan nilai tambah. Mulailah dengan Simple Factory, dan Anda akan segera merasakan manfaatnya!

🔗 Baca Juga