Beberapa orang berpendapat bahwa Pola Singleton selalu merupakan anti-pola. Bagaimana menurut anda?
Beberapa orang berpendapat bahwa Pola Singleton selalu merupakan anti-pola. Bagaimana menurut anda?
Jawaban:
Dua kritik utama Singletons jatuh ke dalam dua kubu dari apa yang saya amati:
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.
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.
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.
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.
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.
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.
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.
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 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.
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:
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.
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.