January 8, 2026 (3mo ago)

Pola desain singleton: Kelebihan, Kekurangan, dan Alternatif Modern

Pelajari pola desain singleton, kekurangannya, dan alternatif modern seperti DI untuk kode TypeScript yang lebih bersih dan dapat diuji.

← Back to blog
Cover Image for Pola desain singleton: Kelebihan, Kekurangan, dan Alternatif Modern

Pelajari pola desain singleton, kekurangannya, dan alternatif modern seperti DI untuk kode TypeScript yang lebih bersih dan dapat diuji.

Pola Singleton: Kekurangan & Alternatif Modern

Ringkasan: Pahami pola singleton, mengapa pola ini sering menyebabkan masalah keterujian dan penskalaan, serta alternatif modern seperti injeksi dependensi dan pola modul untuk TypeScript.

Pendahuluan

Pola singleton memastikan sebuah kelas hanya memiliki satu instance dan menyediakan titik akses global ke instance tersebut. Kenyamanan itu bisa menggoda, tetapi dalam proyek TypeScript dan Node.js modern seringkali menimbulkan kopling tersembunyi, pengujian yang rapuh, dan masalah penskalaan. Artikel ini menjelaskan apa yang dilakukan singleton, mengapa mereka kontroversial, dan alternatif praktis serta dapat diuji yang bisa Anda terapkan hari ini.

Apa yang Sebenarnya Diselesaikan oleh Pola Singleton

Gambar garis rinci menara pengendali lalu lintas udara dikelilingi oleh dua belas pesawat yang berbeda.

Bayangkan sebuah menara pengendali lalu lintas udara yang mengoordinasikan puluhan pesawat. Menara mewakili otoritas tunggal yang mencegah tabrakan dan kebingungan. Demikian pula, sebuah singleton menjamin satu objek bersama untuk kekhawatiran lintas-potong—konfigurasi, logging, atau connection pool—sehingga setiap bagian aplikasi menggunakan sumber terpusat yang sama.

Pola ini menegakkan dua hal:

  • Menjamin satu instance dengan menyembunyikan konstruktor.
  • Menyediakan akses global melalui accessor statis seperti getInstance().

Kombinasi itu memberi Anda kenyamanan global dengan biaya memperkenalkan state global, yang memiliki konsekuensi jangka panjang untuk pemeliharaan dan keterujian1.

Mengapa Pengembang Mengkritik Singleton

Pola singleton sering berperilaku seperti variabel global dengan pembungkus formal. Itu menyebabkan tiga masalah utama:

  1. Ketergantungan tersembunyi: Kode yang memanggil Singleton.getInstance() memiliki ketergantungan yang tak terlihat, membuat komponen lebih sulit untuk dipahami dan digunakan kembali.
  2. Keterujian buruk: State bersama bertahan di antara pengujian dan membuat unit test fluktuatif serta sulit diisolasi.
  3. Kopling arsitektural: Singletons melanggar prinsip seperti Single Responsibility Principle dan Dependency Inversion, yang mengarah pada desain rapuh yang sulit diubah.

Kritik-kritik ini sudah mapan di komunitas rekayasa dan literatur desain1.

Mengimplementasikan Singleton di TypeScript (Klasik)

Diagram yang menggambarkan pola desain Singleton, menunjukkan sebuah kelas dengan konstruktor privat dan metode getInstance() statis yang mengarah ke objek singleton.

Berikut adalah implementasi kanonik agar Anda bisa mengenalinya dalam kode nyata.

class SettingsManager {
  private static instance: SettingsManager;

  private constructor() {
    console.log("SettingsManager instance created.");
  }

  public static getInstance(): SettingsManager {
    if (!SettingsManager.instance) {
      SettingsManager.instance = new SettingsManager();
    }
    return SettingsManager.instance;
  }

  public getSetting(key: string): string {
    return `Value for ${key}`;
  }
}

const config1 = SettingsManager.getInstance();
const config2 = SettingsManager.getInstance();
console.log(config1 === config2); // true

Catatan Keamanan Thread

Di bahasa yang multi-threaded Anda harus melindungi terhadap kondisi race selama inisialisasi. Node.js berjalan pada event loop single-threaded, jadi singleton JavaScript tidak menghadapi risiko konkurensi yang sama pada saat pembuatan, tetapi keamanan thread menjadi perhatian di runtime lain2.

Mengapa Singleton Menjadi Anti-Pattern

Pada skala besar, singletons memperkenalkan state global dan kopling tersembunyi di seluruh basis kode. Ketika sebuah kelas mencari instance global, ketergantungan itu tidak terlihat di konstruktor atau API publik. Anda tidak bisa mengidentifikasi kebutuhan komponen dengan memeriksa antarmukanya, yang membuat penalaran dan refaktorisasi jauh lebih sulit.

Masalah Keterujian

Karena sebuah instance singleton bertahan, pengujian yang bergantung padanya bisa bocor state antar run. Itu membuat unit test rapuh dan tidak deterministik. Strategi pengujian modern lebih menyukai injeksi dependensi agar test double bisa disuntikkan dengan mudah, menjaga pengujian terisolasi dan dapat diulang.

Pelanggaran Prinsip SOLID

Singleton mencampurkan manajemen lifecycle dengan logika bisnis, melanggar Single Responsibility Principle. Mereka juga memaksa kode bergantung pada implementasi konkret daripada abstraksi, bertentangan dengan Dependency Inversion Principle dan membuat komponen lebih sulit untuk diganti atau dimock.

Alternatif yang Lebih Baik

Diagram yang menggambarkan Dependency Injection sebagai alternatif yang lebih baik terhadap pola desain Singleton.

Berikut alternatif praktis dan modern yang sebaiknya Anda pertimbangkan.

Injeksi Dependensi (DI)

Alih-alih sebuah kelas mengambil instance global, DI menyediakan dependensi dari luar. Constructor injection adalah bentuk paling sederhana: kelas yang bergantung mendeklarasikan apa yang dibutuhkannya, dan composition root menghubungkan instance konkret bersama-sama. Ini membuat dependensi eksplisit dan mudah untuk dimock dalam pengujian4.

Refaktor contoh sebelumnya menjadi kelas biasa yang dapat diinstansiasi:

export class SettingsManager {
  constructor() {
    console.log("SettingsManager instance created.");
  }

  public getSetting(key: string): string {
    return `Value for ${key}`;
  }
}

Seorang consumer menerimanya melalui constructor injection:

import { SettingsManager } from './SettingsManager';

export class UserProfile {
  constructor(private settings: SettingsManager) {}

  public loadProfileTheme(): string {
    const theme = this.settings.getSetting('theme');
    return `Theme set to: ${theme}`;
  }
}

Container DI seperti InversifyJS atau framework seperti NestJS bisa mengelola komposisi untuk Anda, atau Anda bisa menghubungkan instance secara manual di titik masuk aplikasi5.

Modul ES

Node dan bundler modern mengevaluasi modul sekali dan meng-cache hasilnya. Mengekspor objek yang sudah diinstansiasi dari sebuah modul memberi Anda instance bersama ringan tanpa boilerplate singleton. Ini sering cocok untuk state bersama sederhana atau utilitas3.

Pola Pabrik (Factory)

Jika logika pembuatan kompleks atau Anda membutuhkan banyak instance terkonfigurasi, sebuah factory memusatkan pembuatan tanpa memaksakan satu instance global. Factory memberi Anda kontrol atas lifecycle sambil menjaga tanggung jawab terpisah.

Perbandingan Singkat

PatternTestabilityFlexibilityBest for
Dependency InjectionExcellentHighLarge apps that need loose coupling and testability
ES ModulesGoodMediumSimple shared state in JS/TS apps
Factory PatternGoodMediumComplex creation logic or multiple configuration variants

Roadmap Refaktor: Mengganti Singleton dengan Aman

Refaktorisasi singleton di basis kode warisan sebaiknya bertahap dan teruji dengan baik.

  1. Identifikasi setiap panggilan ke getInstance() dan peta ketergantungannya.
  2. Ubah kelas singleton menjadi kelas normal dengan membuat konstruktor publik sembari mempertahankan accessor statis sementara.
  3. Perkenalkan strategi injeksi di composition root dan mulai membuat dependensi di sana.
  4. Ganti consumer satu per satu agar menerima dependensi melalui constructor injection, jalankan pengujian setelah setiap perubahan.
  5. Ketika tidak ada pemanggil yang tersisa, hapus accessor statis dan instance.

Pendekatan bertahap berbasis tes ini meminimalkan risiko dan menjaga aplikasi tetap stabil selama migrasi. Menerapkan praktik ini dapat secara signifikan meningkatkan kecepatan pengembang dan ketahanan sistem6.

Singletons dalam Sistem Terdistribusi: Peringatan

Gagasan satu instance runtuh dalam sistem terdistribusi. Setiap proses akan membuat singleton-nya sendiri, yang menggagalkan pola tersebut. Upaya untuk menegakkan singleton global lintas-proses memperkenalkan bottleneck dan titik kegagalan tunggal dan merupakan anti-pattern dalam arsitektur terdistribusi7.

Jika Anda membutuhkan koordinasi antar layanan, pilih primitif koordinasi terdistribusi (database, antrean pesan, layanan konsensus) yang dirancang untuk tujuan itu daripada memaksakan model satu-instance lintas proses.

FAQs — Jawaban Singkat

Q: Kapan singleton dapat diterima?

A: Jarang. Singleton mungkin ok untuk pembungkus yang benar-benar tanpa state di sekitar satu perangkat fisik, tetapi sebagian besar kasus penggunaan (logger, config) lebih baik dilayani dengan DI atau ekspor modul1.

Q: Bukankah singleton hanya menggantikan global dengan cara yang bersih?

A: Terlihat seperti itu, tetapi ia tetap menciptakan state global dan ketergantungan tersembunyi. Pembungkus tidak menyelesaikan masalah inti kopling dan keterujian1.

Q: Bagaimana saya mulai memigrasi singleton di basis kode besar?

A: Peta semua penggunaan, buat konstruktor publik, perkenalkan composition root tingkat atas, dan secara bertahap ganti pemanggil agar menerima dependensi melalui konstruktor. Pertahankan API statis sampai setiap pemanggil bermigrasi, lalu hapus.

Tiga Bagian Tanya Jawab Singkat (Fokus Pengguna)

Q1: Bagaimana singleton merusak pengujian unit?

A1: Karena instance yang sama dibagikan antar pengujian, satu pengujian dapat mengubah state global dan menyebabkan pengujian lain gagal secara tak terduga. DI memungkinkan Anda menyuntikkan test double baru ke setiap pengujian, menjaga mereka terisolasi.

Q2: Apa alternatif paling sederhana untuk proyek TypeScript kecil?

A2: Gunakan ekspor modul ES untuk utilitas bersama atau satu instance. Itu sederhana, tidak memerlukan framework tambahan, dan tetap menghindari pola accessor global eksplisit3.

Q3: Apa yang harus saya gunakan saat membangun microservices yang dapat diskalakan?

A3: Hindari singleton terdistribusi. Pilih layanan tanpa state, koordinasi berbasis pesan, atau sistem terdistribusi yang dibuat untuk tujuan tertentu (penyimpanan data, antrean) untuk mengoordinasikan state antar layanan7.


Di Clean Code Guy, kami membantu tim mengganti pola warisan yang rapuh dengan arsitektur yang dapat dipelihara. Jika basis kode Anda kusut oleh global dan singleton, refaktor yang hati-hati dapat mengembalikan keterujian dan kepercayaan pengembang.

1.
Martin Fowler, “Singleton,” Martin Fowler’s Bliki, https://martinfowler.com/bliki/Singleton.html
2.
Dokumentasi Node.js, “The Node.js Event Loop,” https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
3.
Dokumentasi Node.js, “Modules: Loading from Cache,” https://nodejs.org/api/modules.html#modules_caching
4.
“Dependency injection,” Wikipedia, https://en.wikipedia.org/wiki/Dependency_injection
5.
InversifyJS, Dokumentasi, https://inversify.io/
6.
Nicole Forsgren, Jez Humble, and Gene Kim, “State of DevOps Report 2019,” Accelerate / DORA, https://services.google.com/fh/files/misc/state-of-devops-2019.pdf
7.
The Twelve-Factor App, “Twelve-Factor App methodology,” https://12factor.net/
← Back to blog
🙋🏻‍♂️

AI menulis kode.
Anda membuatnya bertahan.

Di era akselerasi AI, kode bersih bukan hanya praktik yang baik — ini adalah perbedaan antara sistem yang berkembang dan codebase yang runtuh di bawah beratnya sendiri.