Factory Pattern dalam Go: Panduan Lengkap dengan Contoh

Factory Pattern dalam Bahasa Go
Apa itu Factory Pattern?
Factory Pattern adalah creational design pattern yang menyediakan antarmuka untuk membuat objek dalam superclass, tetapi memungkinkan subclass untuk mengubah jenis objek yang akan dibuat.
Pattern ini digunakan ketika:
- Pembuatan objek membutuhkan logika yang kompleks
- Kita ingin mengabstraksi proses instansiasi dari client code
- Kita ingin menyediakan cara yang fleksibel untuk menambahkan jenis objek baru di masa depan
Konsep Inti: Factory Pattern memisahkan proses pembuatan objek dari penggunaannya, sehingga client code tidak perlu mengetahui detail implementasi konkret dari objek yang dibuat.
Analogi: Pabrik Kendaraan
Bayangkan sebuah pabrik kendaraan yang dapat memproduksi berbagai jenis kendaraan (mobil, motor, truk).
- Pembeli tidak perlu tahu bagaimana setiap kendaraan diproduksi
- Pembeli hanya meminta jenis kendaraan yang diinginkan
- Pabrik yang menentukan proses produksi yang tepat
- Jika ada jenis kendaraan baru, pabrik yang menanganinya, bukan pembeli
Implementasi Dasar Factory Pattern di Go
Berikut contoh sederhana implementasi factory pattern untuk membuat berbagai jenis kendaraan:
package main
import "fmt"
// Interface untuk semua produk kendaraan
type Kendaraan interface {
Jalan()
GetNama() string
}
// Struct untuk Mobil
type Mobil struct {
nama string
}
func (m Mobil) Jalan() {
fmt.Printf("%s berjalan dengan roda 4\n", m.nama)
}
func (m Mobil) GetNama() string {
return m.nama
}
// Struct untuk Motor
type Motor struct {
nama string
}
func (m Motor) Jalan() {
fmt.Printf("%s berjalan dengan roda 2\n", m.nama)
}
func (m Motor) GetNama() string {
return m.nama
}
// Factory function untuk membuat kendaraan
func BuatKendaraan(jenis, nama string) Kendaraan {
switch jenis {
case "mobil":
return Mobil{nama: nama}
case "motor":
return Motor{nama: nama}
default:
return nil
}
}
func main() {
// Client code menggunakan factory
kendaraan1 := BuatKendaraan("mobil", "Toyota Avanza")
kendaraan2 := BuatKendaraan("motor", "Honda CBR")
kendaraan1.Jalan() // Output: Toyota Avanza berjalan dengan roda 4
kendaraan2.Jalan() // Output: Honda CBR berjalan dengan roda 2
}
Studi Kasus Nyata: Upload File ke Multiple Provider
Dalam pengembangan aplikasi modern, seringkali kita perlu mengunggah file ke berbagai penyedia penyimpanan (AWS S3, Google Cloud Storage, Azure Blob Storage, atau lokal). Factory Pattern sangat cocok untuk kasus ini.
Implementasi Uploader dengan Factory Pattern
package main
import (
"fmt"
"io"
)
// Interface untuk semua service upload
type Uploader interface {
Upload(fileName string, file io.Reader) (string, error)
Delete(fileName string) error
}
// AWS S3 Uploader
type AWSUploader struct {
bucket string
region string
}
func (a AWSUploader) Upload(fileName string, file io.Reader) (string, error) {
// Implementasi upload ke AWS S3
return fmt.Sprintf("https://%s.s3.%s.amazonaws.com/%s", a.bucket, a.region, fileName), nil
}
func (a AWSUploader) Delete(fileName string) error {
// Implementasi delete dari AWS S3
return nil
}
// Google Cloud Storage Uploader
type GCSUploader struct {
bucket string
project string
}
func (g GCSUploader) Upload(fileName string, file io.Reader) (string, error) {
// Implementasi upload ke Google Cloud Storage
return fmt.Sprintf("https://storage.googleapis.com/%s/%s", g.bucket, fileName), nil
}
func (g GCSUploader) Delete(fileName string) error {
// Implementasi delete dari Google Cloud Storage
return nil
}
// Local Storage Uploader
type LocalUploader struct {
basePath string
}
func (l LocalUploader) Upload(fileName string, file io.Reader) (string, error) {
// Implementasi upload ke local storage
return fmt.Sprintf("%s/%s", l.basePath, fileName), nil
}
func (l LocalUploader) Delete(fileName string) error {
// Implementasi delete dari local storage
return nil
}
// Factory function untuk membuat uploader
func NewUploader(provider string, config map[string]string) (Uploader, error) {
switch provider {
case "aws":
return AWSUploader{
bucket: config["bucket"],
region: config["region"],
}, nil
case "gcs":
return GCSUploader{
bucket: config["bucket"],
project: config["project"],
}, nil
case "local":
return LocalUploader{
basePath: config["basePath"],
}, nil
default:
return nil, fmt.Errorf("provider %s tidak didukung", provider)
}
}
func main() {
// Konfigurasi untuk berbagai provider
awsConfig := map[string]string{
"bucket": "my-bucket",
"region": "us-east-1",
}
gcsConfig := map[string]string{
"bucket": "my-bucket",
"project": "my-project",
}
localConfig := map[string]string{
"basePath": "/uploads",
}
// Membuat uploader berdasarkan provider yang diinginkan
awsUploader, _ := NewUploader("aws", awsConfig)
gcsUploader, _ := NewUploader("gcs", gcsConfig)
localUploader, _ := NewUploader("local", localConfig)
// Client code menggunakan uploader tanpa tahu detail implementasi
url, _ := awsUploader.Upload("document.pdf", nil)
fmt.Println("File diunggah ke:", url)
// Jika di masa depan perlu menambah provider baru (misal Azure),
// kita hanya perlu menambah case di factory function
// tanpa mengubah client code yang sudah ada
}
Keuntungan Factory Pattern
- Decoupling: Memisahkan kode pembuatan objek dari kode bisnis
- Single Responsibility: Memusatkan logika pembuatan objek di satu tempat
- Open/Closed Principle: Mudah menambah jenis objek baru tanpa mengubah kode yang sudah ada
- Konsistensi: Memastikan objek dibuat dengan konfigurasi yang konsisten
Kapan Menggunakan Factory Pattern?
- Ketika proses pembuatan objek kompleks dan melibatkan logika kondisional
- Ketika ingin menyembunyikan detail implementasi dari client
- Ketika perlu membuat berbagai varian dari suatu produk
- Ketika ingin memusatkan kontrol atas pembuatan objek
Kesimpulan
Factory Pattern adalah pattern yang powerful dalam Go untuk:
- Mengabstraksi proses instansiasi objek
- Meningkatkan fleksibilitas dan maintainability kode
- Menerapkan Dependency Inversion Principle
- Memudahkan penambahan jenis objek baru di masa depan
Dalam konteks upload file ke multiple provider, factory pattern memungkinkan kita untuk dengan mudah menambah provider baru tanpa mengubah kode yang sudah ada, sehingga sistem menjadi lebih terbuka untuk ekstensi tetapi tertutup untuk modifikasi.