Kapan Singleton pantas? [Tutup]

Jawaban:

32

Dua kritik utama Singletons jatuh ke dalam dua kubu dari apa yang saya amati:

  1. Singletons disalahgunakan dan disalahgunakan oleh programmer yang kurang mampu sehingga semuanya menjadi singleton dan Anda melihat kode dikotori dengan referensi Class :: get_instance (). Secara umum hanya ada satu atau dua sumber daya (seperti koneksi database misalnya) yang memenuhi syarat untuk penggunaan pola Singleton.
  2. Lajang pada dasarnya adalah kelas statis, bergantung pada satu atau lebih metode dan properti statis. Semua hal statis menghadirkan masalah nyata dan nyata ketika Anda mencoba melakukan Pengujian Unit karena mereka mewakili jalan buntu dalam kode Anda yang tidak dapat diejek atau di-stub. Akibatnya, ketika Anda menguji kelas yang bergantung pada Singleton (atau metode statis lain atau kelas) Anda tidak hanya menguji kelas itu tetapi juga metode atau kelas statis.

Sebagai hasil dari kedua hal ini, pendekatan umum adalah menggunakan membuat objek kontainer yang luas untuk menampung satu instance dari kelas-kelas ini dan hanya objek kontainer yang memodifikasi tipe-tipe kelas ini sementara banyak kelas lain dapat diberikan akses kepada mereka untuk menggunakan dari objek wadah.

Noah Goodrich
sumber
6
Saya hanya ingin mengatakan bahwa Anda dapat mengejek metode statis di Java dengan JMockit ( code.google.com/p/jmockit ). Ini alat yang sangat berguna.
Michael K
3
Beberapa pemikiran: wadahnya adalah Singleton, jadi Anda belum menyingkirkan Singletons. Juga, jika Anda menulis perpustakaan dengan API publik, Anda tidak bisa memaksa pengguna API Anda untuk menggunakan wadah Anda . Jika Anda memerlukan manajer sumber daya global di perpustakaan Anda (melindungi akses ke satu sumber daya fisik, misalnya) maka Anda perlu menggunakan Singleton.
Scott Whitlock
2
@Scott Whitlock mengapa harus menjadi seorang lajang? Hanya karena hanya ada satu instance tidak membuatnya begitu. Setiap pustaka
IoC
Solusi ini diusulkan juga dikenal sebagai Toolbox
cregox
41

Saya setuju bahwa ini anti-pola. Mengapa? Karena memungkinkan kode Anda untuk berbohong tentang dependensinya, dan Anda tidak bisa memercayai programmer lain untuk tidak memperkenalkan keadaan yang bisa berubah dalam singleton Anda yang sebelumnya tidak dapat diubah.

Kelas mungkin memiliki konstruktor yang hanya membutuhkan string, jadi Anda pikir itu dipakai secara terpisah dan tidak memiliki efek samping. Namun, diam-diam, itu berkomunikasi dengan semacam objek singleton publik yang tersedia secara global, sehingga setiap kali Anda membuat instance kelas, itu berisi data yang berbeda. Ini adalah masalah besar, tidak hanya untuk pengguna API Anda, tetapi juga untuk pengujian kode. Untuk unit-test kode dengan benar, Anda perlu mengelola mikro dan menyadari keadaan global dalam singleton, untuk mendapatkan hasil tes yang konsisten.

Magnus Wolffelt
sumber
Seandainya aku bisa lebih baik sekali!
MattDavey
34

Pola Singleton pada dasarnya hanyalah variabel global yang diinisialisasi malas. Variabel global secara umum dan benar dianggap jahat karena memungkinkan tindakan seram pada jarak antara bagian-bagian yang tampaknya tidak terkait suatu program. Namun, IMHO tidak ada yang salah dengan variabel global yang ditetapkan satu kali, dari satu tempat, sebagai bagian dari rutin inisialisasi program (misalnya, dengan membaca file konfigurasi atau argumen baris perintah) dan diperlakukan sebagai konstanta setelahnya. Penggunaan variabel global semacam itu hanya berbeda dalam huruf, bukan dalam semangat, dari memiliki konstanta bernama yang dinyatakan pada waktu kompilasi.

Demikian pula, pendapat saya tentang Singletons adalah bahwa mereka buruk jika dan hanya jika mereka digunakan untuk melewati keadaan yang bisa berubah antara bagian-bagian yang tampaknya tidak berhubungan dari suatu program. Jika mereka tidak mengandung keadaan bisa berubah, atau jika keadaan bisa berubah yang mereka mengandung benar-benar dienkapsulasi sehingga pengguna objek tidak perlu mengetahuinya bahkan dalam lingkungan multithreaded, maka tidak ada yang salah dengan mereka.

dsimcha
sumber
3
Jadi dengan kata lain Anda menentang setiap penggunaan database dalam suatu program.
Kendall Helmstetter Gelner
Ada video bicara teknologi google terkenal yang menggemakan opini Anda youtube.com/watch?v=-FRm3VPhseI
Martin York
2
@KendallHelmstetterGelner: Ada perbedaan antara penyimpanan keadaan dan sekunder. Tidak mungkin Anda dapat menyimpan seluruh DB dalam memori.
Martin York
7

Mengapa orang menggunakannya?

Saya telah melihat cukup banyak lajang di dunia PHP. Saya tidak ingat ada kasus penggunaan di mana saya menemukan pola untuk dibenarkan. Tapi saya pikir saya punya ide tentang motivasi mengapa orang menggunakannya.

  1. Contoh tunggal .

    "Gunakan satu instance kelas C di seluruh aplikasi."

    Ini adalah persyaratan yang masuk akal misalnya untuk "koneksi database default". Itu tidak berarti Anda tidak akan pernah membuat koneksi db kedua, itu hanya berarti Anda biasanya bekerja dengan yang standar.

  2. Instansiasi tunggal .

    "Jangan izinkan kelas C untuk dipakai lebih dari satu kali (per proses, per permintaan, dll)."

    Ini hanya relevan jika membuat instance kelas akan memiliki efek samping yang bertentangan dengan instance lain.

    Seringkali konflik ini dapat dihindari dengan mendesain ulang komponen - misalnya dengan menghilangkan efek samping dari konstruktor kelas. Atau mereka dapat diselesaikan dengan cara lain. Tetapi mungkin masih ada beberapa kasus penggunaan yang sah.

    Anda juga harus memikirkan apakah persyaratan "hanya satu" benar-benar berarti "satu per proses". Misalnya untuk konkurensi sumber daya, persyaratannya agak "satu per seluruh sistem, lintas proses" dan bukan "satu per proses". Dan untuk hal-hal lain itu lebih per "konteks aplikasi", dan Anda hanya memiliki satu konteks aplikasi per proses.

    Kalau tidak, tidak perlu memaksakan asumsi ini. Menegakkan ini juga berarti Anda tidak dapat membuat turunan terpisah untuk pengujian unit.

  3. Akses global.

    Ini sah hanya jika Anda tidak memiliki infrastruktur yang memadai untuk meneruskan objek ke tempat mereka digunakan. Ini bisa berarti bahwa kerangka kerja atau lingkungan Anda payah, tetapi mungkin tidak dalam kemampuan Anda untuk memperbaikinya.

    Harganya ketat, ketergantungan tersembunyi, dan segala sesuatu yang buruk tentang keadaan global. Tetapi Anda mungkin sudah menderita ini.

  4. Instansiasi malas.

    Ini bukan bagian yang diperlukan dari seorang lajang, tetapi tampaknya cara yang paling populer untuk menerapkannya. Tetapi, sementara instantiasi yang malas adalah hal yang baik untuk dimiliki, Anda tidak benar-benar membutuhkan singleton untuk mencapainya.

Implementasi yang khas

Implementasi tipikal adalah kelas dengan konstruktor pribadi, dan variabel instance statis, dan metode getInstance () statis dengan instantiation lazy.

Selain masalah yang disebutkan di atas, ini menggigit dengan prinsip tanggung jawab tunggal , karena kelas memang mengendalikan instantiasi dan siklus hidup mereka sendiri , di samping tanggung jawab lain yang sudah dimiliki kelas.

Kesimpulan

Dalam banyak kasus, Anda dapat mencapai hasil yang sama tanpa singleton, dan tanpa negara global. Sebagai gantinya, Anda harus menggunakan injeksi ketergantungan, dan Anda mungkin ingin mempertimbangkan wadah injeksi ketergantungan .

Namun, ada kasus penggunaan di mana Anda memiliki persyaratan valid berikut yang tersisa:

  • Instansi tunggal (tetapi bukan instantiasi tunggal)
  • Akses global (karena Anda bekerja dengan kerangka usia perunggu)
  • Instantiasi malas (karena itu menyenangkan untuk dimiliki)

Jadi, inilah yang dapat Anda lakukan dalam kasus ini:

  • Buat kelas C yang ingin Anda instantiate, dengan konstruktor publik.

  • Buat kelas S terpisah dengan variabel instance statis dan metode S :: getInstance () statis dengan malas instantiation, yang akan menggunakan kelas C untuk instance.

  • Hilangkan semua efek samping dari konstruktor C. Sebagai gantinya, masukkan efek samping ini dalam metode S :: getInstance ().

  • Jika Anda memiliki lebih dari satu kelas dengan persyaratan di atas, Anda dapat mempertimbangkan untuk mengelola instance kelas dengan wadah layanan lokal kecil , dan menggunakan instance statis hanya untuk wadah. Jadi, S :: getContainer () akan memberi Anda wadah layanan malas-instantiated, dan Anda mendapatkan objek lain dari wadah.

  • Hindari memanggil getInstance statis () di mana Anda bisa. Gunakan injeksi ketergantungan sebagai gantinya, bila memungkinkan. Terutama, jika Anda menggunakan pendekatan wadah dengan banyak objek yang saling bergantung, maka tidak ada yang harus memanggil S :: getContainer ().

  • Secara opsional buat antarmuka yang mengimplementasikan kelas C, dan gunakan ini untuk mendokumentasikan nilai kembalinya S :: getInstance ().

(Apakah kita masih menyebut ini singleton? Saya serahkan ini ke bagian komentar ..)

Manfaat:

  • Anda dapat membuat instance C terpisah untuk pengujian unit, tanpa menyentuh status global apa pun.

  • Manajemen instance dipisahkan dari kelas itu sendiri -> pemisahan masalah, prinsip tanggung jawab tunggal.

  • Akan sangat mudah untuk membiarkan S :: getInstance () menggunakan kelas yang berbeda untuk instance, atau bahkan secara dinamis menentukan kelas mana yang akan digunakan.

donquixote
sumber
1

Secara pribadi saya akan menggunakan lajang ketika saya membutuhkan 1, 2, atau 3, atau sejumlah objek untuk kelas tertentu yang bersangkutan. Atau saya ingin menyampaikan kepada pengguna kelas saya bahwa saya tidak ingin beberapa instance kelas saya dibuat agar berfungsi dengan baik.

Juga saya hanya akan menggunakannya ketika saya perlu menggunakannya hampir di mana-mana dalam kode saya dan saya tidak ingin meneruskan objek sebagai parameter ke setiap kelas atau fungsi yang membutuhkannya.

Selain itu saya hanya akan menggunakan singleton jika tidak merusak transparansi referensial fungsi lain. Artinya diberi beberapa input maka akan selalu menghasilkan output yang sama. Yaitu saya tidak menggunakannya untuk negara global. Kecuali jika keadaan global itu diinisialisasi satu kali dan tidak pernah berubah.

Adapun kapan tidak menggunakannya, lihat 3 di atas dan ubah ke sebaliknya.

Brian R. Bondy
sumber
3
-0 tetapi saya hampir -1. Saya lebih suka meneruskannya di mana-mana dalam kode saya daripada menggunakan singleton TETAPI jika saya benar-benar malas saya dapat melakukannya. Saya akan menggunakan vars global sebagai gantinya jika saya bisa atau anggota statis. vars global benar-benar jauh lebih disukai (bagi saya) daripada lajang. Global tidak berbohong