Jadi lajang itu jahat, lalu apa?

554

Ada banyak diskusi akhir-akhir ini tentang masalah dengan menggunakan (dan menggunakan) Singletons secara berlebihan. Saya telah menjadi salah satu dari orang-orang itu sebelumnya dalam karier saya juga. Saya bisa melihat apa masalahnya sekarang, namun, masih ada banyak kasus di mana saya tidak bisa melihat alternatif yang bagus - dan tidak banyak diskusi anti-Singleton yang benar-benar menyediakannya.

Ini adalah contoh nyata dari proyek besar terbaru yang saya ikuti:

Aplikasi ini adalah klien yang tebal dengan banyak layar dan komponen terpisah yang menggunakan sejumlah besar data dari keadaan server yang tidak terlalu sering diperbarui. Data ini pada dasarnya di-cache dalam objek "manajer" Singleton - "negara global" yang ditakuti. Idenya adalah untuk memiliki satu tempat ini di aplikasi yang menyimpan data disimpan dan disinkronkan, dan kemudian setiap layar baru yang dibuka hanya dapat meminta sebagian besar dari apa yang mereka butuhkan dari sana, tanpa membuat permintaan berulang untuk berbagai data pendukung dari server. Terus-menerus meminta ke server akan memakan terlalu banyak bandwidth - dan saya berbicara ribuan dolar tagihan internet tambahan per minggu, jadi itu tidak dapat diterima.

Apakah ada pendekatan lain yang bisa sesuai di sini daripada pada dasarnya memiliki objek cache data manager global semacam ini? Objek ini tidak secara resmi harus menjadi "Singleton", tetapi secara konseptual masuk akal untuk menjadi "Singleton". Apa alternatif bersih yang bagus di sini?

Tabel Bobby
sumber
10
Apa masalah yang seharusnya dipecahkan oleh penggunaan Singleton? Bagaimana lebih baik dalam memecahkan masalah itu daripada alternatif (seperti kelas statis)?
Anon.
14
@ Anon: Bagaimana menggunakan kelas statis membuat situasi menjadi lebih baik. Masih ada kopling ketat?
Martin York
5
@ Martin: Saya tidak menyarankan itu membuatnya "lebih baik". Saya menyarankan bahwa dalam kebanyakan kasus, singleton adalah solusi dalam mencari masalah.
Anon.
9
@ Anon: Tidak benar. Kelas statis memberi Anda (hampir) tidak ada kontrol atas Instansiasi dan membuat multi-threading bahkan lebih sulit daripada Singletons (karena Anda harus membuat serial akses ke setiap metode individual, bukan hanya instance). Singletons juga dapat setidaknya mengimplementasikan suatu antarmuka, yang tidak dapat dilakukan oleh kelas statis. Kelas statis tentu saja memiliki kelebihan mereka, tetapi dalam hal ini Singleton jelas lebih rendah dari dua kejahatan besar. Kelas statis yang mengimplementasikan keadaan apa pun yang dapat berubah seperti neon besar yang berkedip "PERINGATAN: BAD DESIGN DEPAN!" tanda.
Aaronaught
7
@Aaronaught: Jika Anda hanya menyinkronkan akses ke singleton, maka konkurensi Anda rusak . Utas Anda dapat terputus tepat setelah mengambil objek tunggal, utas lain muncul, dan salah, kondisi balapan. Menggunakan Singleton alih-alih kelas statis, dalam banyak kasus, hanya menghilangkan tanda-tanda peringatan dan berpikir yang menyelesaikan masalah .
Anon.

Jawaban:

809

Penting untuk membedakan di sini antara instance tunggal dan pola desain Singleton .

Contoh tunggal hanyalah kenyataan. Sebagian besar aplikasi hanya dirancang untuk bekerja dengan satu konfigurasi pada satu waktu, satu UI pada suatu waktu, satu sistem file pada suatu waktu, dan sebagainya. Jika ada banyak status atau data yang harus dipelihara, maka tentu Anda ingin memiliki satu contoh dan tetap hidup selama mungkin.

Pola desain Singleton adalah jenis single instance yang sangat spesifik, khususnya yang adalah:

  • Dapat diakses melalui bidang instance global, statis;
  • Dibuat baik pada inisialisasi program atau pada akses pertama;
  • Tidak ada konstruktor publik (tidak dapat membuat instantiate secara langsung);
  • Tidak pernah secara eksplisit dibebaskan (secara implisit dibebaskan pada penghentian program).

Karena pilihan desain khusus inilah maka pola tersebut memperkenalkan beberapa potensi masalah jangka panjang:

  • Ketidakmampuan untuk menggunakan kelas abstrak atau antarmuka;
  • Ketidakmampuan untuk subkelas;
  • Kopling tinggi di seluruh aplikasi (sulit untuk dimodifikasi);
  • Sulit untuk diuji (tidak dapat berpura-pura / mengejek dalam unit test);
  • Sulit untuk diparalelkan dalam kasus keadaan bisa berubah (membutuhkan penguncian yang luas);
  • dan seterusnya.

Tidak satu pun dari gejala-gejala ini yang benar-benar endemik pada kejadian tunggal, hanya pola Singleton.

Apa yang bisa kamu lakukan? Cukup tidak menggunakan pola Singleton.

Mengutip dari pertanyaan:

Idenya adalah untuk memiliki satu tempat ini di aplikasi yang menyimpan data disimpan dan disinkronkan, dan kemudian setiap layar baru yang dibuka hanya dapat meminta sebagian besar dari apa yang mereka butuhkan dari sana, tanpa membuat permintaan berulang untuk berbagai data pendukung dari server. Terus-menerus meminta ke server akan memakan terlalu banyak bandwidth - dan saya berbicara ribuan dolar tagihan internet tambahan per minggu, jadi itu tidak dapat diterima.

Konsep ini memiliki nama, saat Anda mengisyaratkan tetapi terdengar tidak pasti. Ini disebut cache . Jika Anda ingin menjadi mewah, Anda dapat menyebutnya "cache offline" atau hanya salinan offline data jarak jauh.

Cache tidak harus berupa singleton. Ini mungkin perlu satu contoh jika Anda ingin menghindari mengambil data yang sama untuk beberapa contoh tembolok; tetapi itu tidak berarti Anda benar-benar harus memaparkan semuanya kepada semua orang .

Hal pertama yang saya lakukan adalah memisahkan area fungsional yang berbeda dari cache menjadi antarmuka yang terpisah. Misalnya, katakanlah Anda membuat klon YouTube terburuk di dunia berdasarkan Microsoft Access:

                          MSAccessCache
                                ▲
                                |
              + ----------------- + ----------------- +
              | | |
         IMediaCache IProfileCache IPageCache
              | | |
              | | |
          VideoPage MyAccountPage MostPopularPage

Di sini Anda memiliki beberapa antarmuka menggambarkan spesifik jenis data kelas tertentu mungkin perlu akses ke - media, profil pengguna, dan halaman statis (seperti halaman depan). Semua itu diimplementasikan oleh satu mega-cache, tetapi Anda mendesain kelas individual Anda untuk menerima antarmuka sebagai gantinya, sehingga mereka tidak peduli seperti apa instance yang mereka miliki. Anda menginisialisasi instance fisik sekali, ketika program Anda mulai, dan kemudian mulai membagikan instance (dilemparkan ke jenis antarmuka tertentu) melalui konstruktor dan properti publik.

Omong -omong, ini disebut Injeksi Ketergantungan. Anda tidak perlu menggunakan Spring atau wadah IoC khusus, asalkan desain kelas umum Anda menerima dependensinya dari penelepon alih-alih membuat instance mereka sendiri atau merujuk negara global .

Mengapa Anda harus menggunakan desain berbasis antarmuka? Tiga alasan:

  1. Itu membuat kode lebih mudah dibaca; Anda dapat dengan jelas memahami dari antarmuka data apa yang bergantung pada kelas dependen.

  2. Jika dan ketika Anda menyadari bahwa Microsoft Access bukan pilihan terbaik untuk back-end data, Anda dapat menggantinya dengan sesuatu yang lebih baik - katakanlah SQL Server.

  3. Jika dan ketika Anda menyadari bahwa SQL Server bukan pilihan terbaik untuk media secara khusus , Anda dapat memecah implementasi Anda tanpa mempengaruhi bagian lain dari sistem . Di situlah kekuatan abstraksi yang sesungguhnya muncul.

Jika Anda ingin mengambil satu langkah lebih jauh maka Anda dapat menggunakan wadah IoC (kerangka kerja DI) seperti Spring (Java) atau Unity (.NET). Hampir setiap kerangka DI akan melakukan manajemen seumur hidupnya sendiri dan secara khusus memungkinkan Anda untuk mendefinisikan layanan tertentu sebagai satu contoh (sering menyebutnya "singleton", tetapi itu hanya untuk keakraban). Pada dasarnya kerangka kerja ini menyelamatkan Anda sebagian besar dari pekerjaan monyet untuk secara manual menyebarkan instance, tetapi mereka tidak sepenuhnya diperlukan. Anda tidak memerlukan alat khusus untuk mengimplementasikan desain ini.

Demi kelengkapan, saya harus menunjukkan bahwa desain di atas juga tidak ideal. Ketika Anda berurusan dengan cache (seperti Anda), Anda sebenarnya harus memiliki layer yang sepenuhnya terpisah . Dengan kata lain, desain seperti ini:

                                                        + - IMediaRepository
                                                        |
                          Cache (Generic) --------------- + - IProfileRepository
                                ▲ |
                                | + - IPageRepository
              + ----------------- + ----------------- +
              | | |
         IMediaCache IProfileCache IPageCache
              | | |
              | | |
          VideoPage MyAccountPage MostPopularPage

Manfaat dari ini adalah Anda bahkan tidak perlu memecah Cacheinstance Anda jika Anda memutuskan untuk refactor; Anda dapat mengubah cara Media disimpan hanya dengan memberinya implementasi alternatif IMediaRepository. Jika Anda berpikir tentang bagaimana ini cocok bersama, Anda akan melihat bahwa itu hanya pernah membuat satu instance fisik dari cache, jadi Anda tidak perlu mengambil data yang sama dua kali.

Tidak satu pun dari hal ini yang mengatakan bahwa setiap bagian dari perangkat lunak di dunia perlu dirancang sesuai dengan standar kohesi tinggi dan kopling longgar ini; itu tergantung pada ukuran dan ruang lingkup proyek, tim Anda, anggaran Anda, tenggat waktu, dll. Tetapi jika Anda bertanya apa desain terbaik (untuk digunakan sebagai pengganti singleton), maka ini dia.

PS Seperti yang telah dinyatakan orang lain, mungkin bukan ide terbaik bagi kelas-kelas dependen untuk menyadari bahwa mereka menggunakan cache - itu adalah detail implementasi yang seharusnya tidak pernah mereka pedulikan. Yang sedang berkata, keseluruhan arsitektur masih akan terlihat sangat mirip dengan apa yang digambarkan di atas, Anda hanya tidak akan merujuk ke antarmuka individu sebagai Cache . Alih-alih, Anda akan menamainya Layanan atau yang serupa.

Aaronaught
sumber
131
Posting pertama yang pernah saya baca yang sebenarnya menjelaskan DI sebagai alternatif negara global. Terima kasih atas waktu dan upaya yang dilakukan untuk ini. Kita semua lebih baik karena pos ini.
MrLane
4
Mengapa Cache tidak bisa menjadi lajang? Apakah ini bukan singleton jika Anda menyebarkannya dan menggunakan injeksi ketergantungan? Singleton hanya membatasi diri kita pada satu contoh, bukan tentang bagaimana mengaksesnya, bukan? Lihat pendapat saya tentang ini: assoc.tumblr.com/post/51302471844/the-misunderstood-singleton
Erik Engheim
29
@AdamSmith: Apakah Anda benar-benar membaca setiap jawaban ini? Pertanyaan Anda dijawab dalam dua paragraf pertama. Pola Singleton! == Mesin Virtual Tunggal.
Aaronaught
5
@Cawas dan Adam Smith - Membaca tautan Anda Saya merasa Anda tidak benar-benar membaca jawaban ini - semuanya sudah ada di sana.
Wilbert
19
@ Cawas Saya merasa bahwa daging dari jawaban ini adalah perbedaan antara turunan tunggal dan tunggal. Singleton itu buruk, satu contoh tidak. Dependency Injection adalah cara umum yang baik untuk menggunakan kejadian tunggal tanpa harus menggunakan lajang.
Wilbert
48

Dalam kasus yang Anda berikan, sepertinya penggunaan Singleton bukanlah masalahnya, tetapi gejala dari suatu masalah - masalah arsitektur yang lebih besar.

Mengapa layar meminta objek cache untuk data? Caching harus transparan kepada klien. Seharusnya ada abstraksi yang sesuai untuk menyediakan data, dan implementasi abstraksi itu mungkin menggunakan caching.

Masalahnya adalah kemungkinan bahwa dependensi antara bagian-bagian sistem tidak diatur dengan benar, dan ini mungkin sistemik.

Mengapa layar harus memiliki pengetahuan tentang dari mana mereka mendapatkan data mereka? Mengapa layar tidak dilengkapi dengan objek yang dapat memenuhi permintaan data (di belakangnya ada cache yang disembunyikan)? Seringkali tanggung jawab untuk membuat layar tidak terpusat, sehingga tidak ada gunanya menyuntikkan dependensi.

Sekali lagi, kami melihat masalah arsitektur dan desain berskala besar.

Juga, sangat penting untuk memahami bahwa masa hidup suatu objek dapat sepenuhnya dipisahkan dari bagaimana benda itu ditemukan untuk digunakan.

Tembolok harus hidup sepanjang masa pakai aplikasi (agar berguna), sehingga masa pakai objek adalah masa pakai Singleton.

Tetapi masalah dengan Singleton (setidaknya implementasi umum Singleton sebagai kelas / properti statis), adalah bagaimana kelas lain yang menggunakannya pergi untuk menemukannya.

Dengan implementasi Singleton statis, konvensi adalah untuk menggunakannya di mana saja diperlukan. Tapi itu benar-benar menyembunyikan ketergantungan dan pasangan erat dua kelas.

Jika kami menyediakan dependensi ke kelas, dependensi itu eksplisit dan semua kelas konsumsi yang perlu memiliki pengetahuan adalah kontrak yang tersedia untuk digunakan.

quentin-starin
sumber
2
Ada sejumlah besar data yang mungkin dibutuhkan layar tertentu, tetapi tidak perlu. Dan Anda tidak tahu sampai tindakan pengguna diambil yang menentukan ini - dan ada banyak, banyak kombinasi. Jadi cara itu dilakukan adalah memiliki beberapa data global umum yang disimpan dalam cache dan disinkronkan di klien (sebagian besar diperoleh saat login), dan kemudian permintaan selanjutnya membangun cache lebih banyak, karena data yang diminta secara eksplisit cenderung digunakan kembali di sesi yang sama. Fokusnya adalah pada pengurangan permintaan ke server, karenanya kebutuhan untuk cache sisi klien. <cont>
Bobby Tables
1
<cont> Ini pada dasarnya IS transparan. Dalam arti ada panggilan balik dari server jika data tertentu yang dibutuhkan belum di-cache. Tetapi implementasi (secara logis dan fisik) dari cache manager itu adalah Singleton.
Bobby Tables
6
Saya dengan qstarin di sini: Objek yang mengakses data tidak boleh tahu (atau perlu tahu) bahwa data di-cache (itu adalah detail implementasi). Pengguna data hanya meminta data (atau meminta antarmuka untuk mengambil data).
Martin York
1
Caching IS pada dasarnya adalah detail implementasi. Ada antarmuka di mana data ditanyai, dan objek yang mendapatkannya tidak tahu apakah itu berasal dari cache atau tidak. Tetapi di bawah cache manager ini adalah Singleton.
Bobby Tables
2
@Obby Tables: maka situasimu tidak separah kelihatannya. Singleton itu (dengan asumsi Anda maksud kelas statis, bukan hanya objek dengan instance yang hidup selama aplikasi) masih bermasalah. Itu menyembunyikan fakta bahwa objek penyedia data Anda memiliki ketergantungan pada penyedia cache. Lebih baik jika itu eksplisit dan dieksternalisasi. Pisahkan mereka. Sangat penting untuk testabilitas bahwa Anda dapat dengan mudah mengganti komponen, dan penyedia cache adalah contoh utama komponen tersebut (seberapa sering penyedia cache didukung oleh ASP.Net).
quentin-starin
45

Saya menulis seluruh bab tentang pertanyaan ini. Sebagian besar dalam konteks permainan, tetapi sebagian besar harus diterapkan di luar permainan.

tl; dr:

Pola Geng Empat Singleton melakukan dua hal: memberi Anda akses mudah ke objek dari mana saja, dan memastikan bahwa hanya satu instance yang dapat dibuat. 99% dari waktu, yang Anda pedulikan adalah paruh pertama itu, dan pengangkutan sepanjang paruh kedua untuk mendapatkannya menambah batasan yang tidak perlu.

Tidak hanya itu, tetapi ada solusi yang lebih baik untuk memberikan akses yang nyaman. Membuat objek global adalah opsi nuklir untuk menyelesaikannya, dan membuatnya mudah untuk menghancurkan enkapsulasi Anda. Segala sesuatu yang buruk tentang global berlaku sepenuhnya untuk para lajang.

Jika Anda menggunakannya hanya karena Anda memiliki banyak tempat di kode yang perlu menyentuh objek yang sama, mencoba untuk menemukan cara yang lebih baik untuk memberikannya kepada hanya objek-objek tanpa mengekspos ke seluruh basis kode. Solusi lain:

  • Selesaikan seluruhnya. Saya telah melihat banyak kelas singleton yang tidak memiliki status negara dan hanya sekumpulan fungsi pembantu. Itu tidak membutuhkan contoh sama sekali. Buat saja mereka fungsi statis, atau pindahkan ke salah satu kelas yang diambil fungsi sebagai argumen. Anda tidak akan memerlukan Mathkelas khusus jika Anda bisa melakukannya 123.Abs().

  • Bagikan itu. Solusi sederhana jika suatu metode membutuhkan beberapa objek lain adalah dengan meneruskannya. Tidak ada yang salah dengan melewatkan beberapa objek di sekitarnya.

  • Letakkan di kelas dasar. Jika Anda memiliki banyak kelas yang semuanya membutuhkan akses ke beberapa objek khusus, dan mereka berbagi kelas dasar, Anda dapat menjadikan objek itu anggota di pangkalan. Ketika Anda membangunnya, masukkan objek. Sekarang semua benda yang diturunkan bisa mendapatkannya saat mereka membutuhkannya. Jika Anda membuatnya terlindungi, Anda memastikan objek masih tetap dienkapsulasi.

banyak sekali
sumber
1
Bisakah Anda meringkas di sini?
Nicole
1
Selesai, tapi saya masih mendorong Anda untuk membaca seluruh bab.
murah hati
4
alternatif lain: Ketergantungan Injeksi!
Brad Cupit
1
@BradCupit dia berbicara tentang itu di tautan juga ... Saya harus mengatakan saya masih mencoba untuk mencerna semua itu. Tetapi ini adalah bacaan yang paling jelas tentang lajang yang pernah saya baca. Sampai sekarang, saya lajang positif yang diperlukan, seperti vars global, dan saya mempromosikan satu Toolbox . Sekarang saya tidak tahu lagi. Mr munificient dapatkah Anda memberi tahu saya jika pelacak layanan hanyalah kotak alat statis ? Bukankah lebih baik menjadikannya singleton (dengan demikian, kotak alat)?
cregox
1
"Toolbox" terlihat sangat mirip dengan "Service Locator" bagi saya. Apakah Anda menggunakan statis untuk itu, atau membuatnya sendiri singleton, saya pikir, tidak begitu penting untuk sebagian besar program. Saya cenderung condong ke statika karena mengapa berurusan dengan inisialisasi malas dan alokasi tumpukan jika Anda tidak perlu?
murah hati
21

Bukan negara global yang menjadi masalahnya.

Sungguh, Anda hanya perlu khawatir global mutable state. Keadaan konstan tidak terpengaruh oleh pengaruh samping dan dengan demikian kurang menjadi masalah.

Perhatian utama dengan singleton adalah bahwa itu menambah kopling dan dengan demikian membuat hal-hal seperti pengujian sulit. Anda dapat mengurangi sambungan dengan mendapatkan singleton dari sumber lain (mis. Pabrik). Ini akan memungkinkan Anda memisahkan kode dari contoh tertentu (meskipun Anda menjadi lebih terhubung ke pabrik (tapi setidaknya pabrik dapat memiliki implementasi alternatif untuk fase yang berbeda)).

Dalam situasi Anda, saya pikir Anda bisa lolos begitu saja selama singleton Anda benar-benar mengimplementasikan antarmuka (sehingga alternatif dapat digunakan dalam situasi lain).

Tetapi penarikan utama lainnya dengan lajang adalah bahwa begitu mereka berada di tempat mengeluarkan mereka dari kode dan menggantinya dengan sesuatu yang lain menjadi tugas yang sangat sulit (ada kopling lagi).

// Example from 5 minutes (con't be too critical)
class ServerFactory
{
    public:
        // By default return a RealServer
        ServerInterface& getServer();

        // Set a non default server:
        void setServer(ServerInterface& server);
};

class ServerInterface { /* define Interface */ };

class RealServer: public ServerInterface {}; // This is a singleton (potentially)

class TestServer: public ServerInterface {}; // This need not be.
Martin York
sumber
Itu masuk akal. Ini juga membuat saya berpikir bahwa saya tidak pernah benar-benar melecehkan Singletons, tetapi baru saja mulai meragukan APAPUN penggunaan mereka. Tetapi saya tidak bisa memikirkan penyalahgunaan yang telah saya lakukan, menurut poin-poin ini. :)
Bobby Tables
2
(Anda mungkin berarti per se tidak "per mengatakan")
nohat
4
@nohat: Saya penutur asli dari "Bahasa Inggris Queens" dan karenanya menolak segala sesuatu yang tampak Prancis kecuali kita membuatnya lebih baik (seperti le weekendoops itu salah satu dari kita). Terima kasih :-)
Martin York
21
per se adalah latin.
Anon.
2
@Anon: Oke. Itu tidak terlalu buruk ;-)
Martin York
19

Lalu apa? Karena tidak ada yang mengatakannya: Toolbox . Itu jika Anda ingin variabel global .

Penyalahgunaan singleton dapat dihindari dengan melihat masalah dari sudut yang berbeda. Misalkan suatu aplikasi hanya membutuhkan satu instance kelas dan aplikasi mengkonfigurasi kelas itu pada saat startup: Mengapa kelas itu sendiri harus bertanggung jawab untuk menjadi seorang lajang? Tampaknya cukup logis bagi aplikasi untuk mengambil tanggung jawab ini, karena aplikasi tersebut memerlukan perilaku semacam ini. Aplikasi, bukan komponen, harus menjadi singleton. Aplikasi kemudian membuat instance komponen yang tersedia untuk setiap kode spesifik aplikasi untuk digunakan. Ketika suatu aplikasi menggunakan beberapa komponen seperti itu, ia dapat menggabungkannya menjadi apa yang kita sebut kotak alat.

Sederhananya, kotak alat aplikasi adalah tunggal yang bertanggung jawab untuk mengonfigurasi sendiri atau untuk mengizinkan mekanisme startup aplikasi untuk mengonfigurasinya ...

public class Toolbox {
     private static Toolbox _instance; 

     public static Toolbox Instance {
         get {
             if (_instance == null) {
                 _instance = new Toolbox(); 
             }
             return _instance; 
         }
     }

     protected Toolbox() {
         Initialize(); 
     }

     protected void Initialize() {
         // Your code here
     }

     private MyComponent _myComponent; 

     public MyComponent MyComponent() {
         get {
             return _myComponent(); 
         }
     }
     ... 

     // Optional: standard extension allowing
     // runtime registration of global objects. 
     private Map components; 

     public Object GetComponent (String componentName) {
         return components.Get(componentName); 
     }

     public void RegisterComponent(String componentName, Object component) 
     {
         components.Put(componentName, component); 
     }

     public void DeregisterComponent(String componentName) {
         components.Remove(componentName); 
     }

}

Tapi coba tebak? Itu adalah singleton!

Dan apa itu singleton?

Mungkin di situlah kebingungan dimulai.

Bagi saya, singleton adalah objek yang diberlakukan untuk memiliki satu instance saja dan selalu. Anda dapat mengaksesnya di mana saja, kapan saja, tanpa harus membuat instantiate. Itu sebabnya itu terkait erat dengan static. Sebagai perbandingan, staticpada dasarnya hal yang sama, kecuali itu bukan contoh. Kita tidak perlu membuat instance, kita bahkan tidak bisa, karena itu dialokasikan secara otomatis. Dan itu bisa dan memang membawa masalah.

Dari pengalaman saya, menggantikan staticSingleton memecahkan banyak masalah dalam proyek tas kain perca ukuran sedang yang saya jalani. Itu hanya berarti memiliki beberapa penggunaan untuk proyek yang dirancang buruk. Saya pikir ada terlalu banyak diskusi jika pola tunggal adalah berguna atau tidak dan saya tidak bisa benar-benar berpendapat jika itu memang buruk . Tetapi masih ada argumen yang baik yang mendukung singleton daripada metode statis, secara umum .

Satu-satunya hal yang saya yakin buruk tentang lajang, adalah ketika kita menggunakannya sambil mengabaikan praktik yang baik. Itu memang sesuatu yang tidak mudah untuk dihadapi. Tetapi praktik buruk dapat diterapkan pada pola apa pun. Dan, saya tahu, terlalu umum untuk mengatakan bahwa ... Maksud saya terlalu banyak.

Jangan salah sangka!

Sederhananya, seperti vars global , lajang harus tetap dihindari setiap saat . Khususnya karena mereka terlalu dilecehkan. Tetapi vars global tidak dapat selalu dihindari dan kita harus menggunakannya dalam kasus terakhir itu.

Lagi pula, ada banyak saran lain selain Toolbox, dan seperti toolbox, masing-masing memiliki aplikasinya ...

Alternatif lain

  • The Artikel terbaik yang saya baru saja membaca tentang lajang menyarankan Service Locator sebagai alternatif. Bagi saya itu pada dasarnya adalah " Kotak Alat Statis ", jika Anda mau. Dengan kata lain, buat Penentu Lokasi Layanan menjadi Singleton dan Anda memiliki Kotak Alat. Itu bertentangan dengan saran awalnya untuk menghindari singleton, tentu saja, tapi itu hanya untuk menegakkan masalah singleton adalah bagaimana ia digunakan, bukan pola itu sendiri.

  • Yang lain menyarankan Pola Pabrik sebagai alternatif. Itu adalah alternatif pertama yang saya dengar dari seorang kolega dan kami dengan cepat menghilangkannya untuk penggunaan kami sebagai var global . Memang memiliki penggunaannya, tetapi begitu juga lajang.

Kedua alternatif di atas adalah alternatif yang baik. Tetapi itu semua tergantung pada penggunaan Anda.

Sekarang, menyiratkan lajang harus dihindari di semua biaya hanya salah ...

  • Jawaban Aaronaught adalah menyarankan untuk tidak pernah menggunakan lajang , karena serangkaian alasan. Tapi mereka semua alasan terhadap bagaimana itu digunakan dan disalahgunakan, tidak langsung terhadap pola itu sendiri. Saya setuju dengan semua kekhawatiran tentang poin-poin itu, bagaimana saya tidak bisa? Saya hanya berpikir itu menyesatkan.

Ketidakmampuan (abstrak atau subkelas) memang ada, tapi lalu apa? Bukan untuk itu. Tidak ada ketidakmampuan untuk antarmuka , sejauh yang saya tahu . Kopling tinggi juga bisa ada di sana, tapi itu hanya karena cara umum digunakan. Tidak harus . Faktanya, sambungan itu sendiri tidak ada hubungannya dengan pola singleton. Yang sedang diklarifikasi, itu juga sudah menghilangkan kesulitan untuk menguji. Adapun kesulitan untuk memparalelkan, itu tergantung pada bahasa dan platform sehingga, sekali lagi, bukan masalah pada polanya.

Contoh-contoh praktis

Saya sering melihat 2 digunakan, baik mendukung dan melawan lajang. Cache web ( kasing saya) dan layanan log .

Penebangan, beberapa akan berpendapat , adalah contoh tunggal yang sempurna, karena, dan saya kutip:

  • Pemohon membutuhkan objek terkenal untuk mengirim permintaan untuk login. Ini berarti titik akses global.
  • Karena layanan pencatatan merupakan sumber peristiwa tunggal yang dapat didaftarkan banyak pendengar, hanya perlu satu contoh.
  • Meskipun aplikasi yang berbeda dapat masuk ke perangkat output yang berbeda, cara mereka mendaftarkan pendengar mereka selalu sama. Semua kustomisasi dilakukan melalui pendengar. Klien dapat meminta penebangan tanpa mengetahui bagaimana atau di mana teks akan dicatat. Karena itu setiap aplikasi akan menggunakan layanan logging dengan cara yang persis sama.
  • Aplikasi apa pun harus dapat lolos hanya dengan satu instance dari layanan logging.
  • Objek apa pun bisa menjadi pemohon log masuk, termasuk komponen yang dapat digunakan kembali, sehingga tidak boleh digabungkan ke aplikasi tertentu.

Sementara yang lain akan berpendapat sulit untuk memperluas layanan log begitu Anda akhirnya menyadari bahwa itu seharusnya tidak hanya satu contoh.

Baiklah, saya katakan kedua argumen itu valid. Masalahnya di sini, sekali lagi, bukan pada pola tunggal. Ada pada keputusan arsitektur dan pembobotan jika refactoring adalah risiko yang layak. Ini masalah lebih lanjut ketika, biasanya, refactoring adalah tindakan korektif yang terakhir dibutuhkan.

cregox
sumber
@gnat, terima kasih! Saya hanya berpikir dalam mengedit jawaban untuk menambahkan beberapa peringatan tentang menggunakan Singletons ... Kutipan Anda sangat cocok!
cregox
2
senang kamu menyukainya. Tidak yakin apakah ini akan membantu untuk menghindari downvotes - mungkin pembaca mengalami kesulitan menghubungkan titik yang dibuat dalam posting ini dengan masalah nyata yang dijelaskan dalam pertanyaan, terutama mengingat analisis hebat yang diberikan dalam jawaban sebelumnya
nyamuk
@gnat ya, saya tahu ini adalah pertarungan yang panjang. Saya harap waktu akan memberi tahu. ;-)
cregox
1
Saya setuju dengan arahan umum Anda di sini, meskipun mungkin agak terlalu antusias tentang perpustakaan yang tampaknya tidak lebih dari wadah IoC yang sangat sederhana (bahkan lebih mendasar daripada, misalnya, Funq ). Service Locator sebenarnya anti-pola; itu adalah alat yang berguna terutama dalam proyek-proyek warisan / brownfield, bersama dengan "DI-orang miskin", di mana akan terlalu mahal untuk memperbaiki segala sesuatu untuk menggunakan wadah IoC dengan benar.
Aaronaught
1
@ Cawas: Ah, maaf - bingung java.awt.Toolkit. ToolboxMaksud saya adalah sama: kedengarannya seperti tas ambil dari potongan-potongan yang tidak terkait, bukan kelas yang koheren dengan tujuan tunggal. Tidak terdengar seperti desain yang bagus untuk saya. (Perhatikan bahwa artikel yang Anda rujuk berasal dari tahun 2001, sebelum injeksi ketergantungan dan wadah DI menjadi hal biasa.)
Jon Skeet
5

Masalah utama saya dengan pola desain tunggal adalah sangat sulit untuk menulis unit test yang baik untuk aplikasi Anda.

Setiap komponen yang memiliki ketergantungan pada "manajer" ini melakukannya dengan menanyakan instance singleton-nya. Dan jika Anda ingin menulis unit test untuk komponen seperti itu Anda harus menyuntikkan data ke instance tunggal ini, yang mungkin tidak mudah.

Jika di sisi lain "manajer" Anda disuntikkan ke dalam komponen dependen melalui parameter konstruktor, dan komponen itu tidak tahu tipe konkret manajer, hanya antarmuka, atau kelas dasar abstrak yang diimplementasikan oleh manajer, lalu sebuah unit tes dapat menyediakan implementasi alternatif manajer ketika menguji dependensi.

Jika Anda menggunakan wadah IOC untuk mengkonfigurasi dan membuat instance komponen yang membentuk aplikasi Anda, maka Anda dapat dengan mudah mengkonfigurasi wadah IOC Anda untuk membuat hanya satu instance dari "manajer", yang memungkinkan Anda untuk mencapai yang sama, hanya satu contoh mengendalikan cache aplikasi global .

Tetapi jika Anda tidak peduli dengan unit test, maka pola desain tunggal bisa baik-baik saja. (Tapi saya tidak akan melakukannya)

Pete
sumber
Dijelaskan dengan baik, ini adalah jawaban yang paling menjelaskan masalah tentang pengujian Singletons
José Tomás Tocino
4

Singleton tidak pada dasarnya buruk , dalam arti bahwa apa pun desain komputasi bisa baik atau buruk. Itu hanya bisa benar (memberikan hasil yang diharapkan) atau tidak. Ini juga bisa bermanfaat atau tidak, jika itu membuat kode lebih jelas atau lebih efisien.

Satu kasus di mana lajang berguna adalah ketika mereka mewakili suatu entitas yang benar-benar unik. Di sebagian besar lingkungan, database unik, hanya ada satu database. Menyambung ke basis data itu mungkin rumit karena memerlukan izin khusus, atau melintasi beberapa jenis koneksi. Mengorganisasikan koneksi itu menjadi singleton mungkin masuk akal karena alasan ini saja.

Tetapi Anda juga perlu memastikan bahwa singleton benar-benar singleton, dan bukan variabel global. Ini penting ketika basis data tunggal dan unik benar-benar 4 basis data, masing-masing satu untuk produksi, pementasan, pengembangan, dan perlengkapan pengujian. Basis data Singleton akan mencari tahu dari siapa mereka harus terhubung, ambil satu contoh untuk database itu, sambungkan jika diperlukan, dan mengembalikannya ke pemanggil.

Ketika seorang lajang bukan benar-benar seorang lajang (ini adalah saat kebanyakan programmer merasa kesal), itu adalah global yang malas dipakai, tidak ada kesempatan untuk menyuntikkan instance yang benar.

Fitur lain yang bermanfaat dari pola singleton yang dirancang dengan baik adalah sering tidak dapat diamati. Penelepon meminta koneksi. Layanan yang menyediakannya dapat mengembalikan objek yang dikumpulkan, atau jika sedang melakukan tes, itu dapat membuat yang baru untuk setiap penelepon, atau menyediakan objek tiruan.

SingleNegationElimination
sumber
3

Penggunaan pola singleton yang mewakili objek aktual sangat dapat diterima. Saya menulis untuk iPhone, dan ada banyak lajang dalam kerangka kerja Cocoa Touch. Aplikasi itu sendiri diwakili oleh singleton kelas UIApplication. Hanya ada satu aplikasi yang Anda, jadi pantas untuk mewakili itu dengan singleton.

Menggunakan singleton sebagai kelas pengelola data tidak masalah selama itu dirancang dengan benar. Jika ini adalah kumpulan properti data, itu tidak lebih baik dari lingkup global. Jika satu set getter dan setter, itu lebih baik, tapi masih tidak bagus. Jika itu adalah kelas yang benar-benar mengelola semua antarmuka data, termasuk mungkin mengambil data jarak jauh, caching, pengaturan, dan teardown ... Itu bisa sangat berguna.

Dan Ray
sumber
2

Lajang hanya proyeksi dari arsitektur berorientasi layanan ke dalam suatu program.

API adalah contoh singleton di tingkat protokol. Anda mengakses Twitter, Google dll melalui apa yang pada dasarnya adalah lajang. Jadi mengapa lajang menjadi buruk dalam suatu program?

Itu tergantung pada bagaimana Anda memikirkan suatu program. Jika Anda menganggap suatu program sebagai masyarakat layanan alih-alih instance yang di-cache secara acak, lajang masuk akal.

Lajang adalah titik akses layanan. Antarmuka publik ke perpustakaan fungsionalitas yang terikat rapat yang mungkin menyembunyikan arsitektur internal yang sangat canggih.

Jadi saya tidak melihat singleton berbeda dari pabrik. Singleton dapat memiliki parameter konstruktor yang dilewatkan. Ini dapat dibuat oleh beberapa konteks yang tahu bagaimana menyelesaikan printer default terhadap semua mekanisme seleksi yang mungkin, misalnya. Untuk pengujian Anda dapat memasukkan tiruan Anda sendiri. Jadi itu bisa sangat fleksibel.

Kuncinya adalah secara internal dalam suatu program ketika saya menjalankan dan membutuhkan sedikit fungsionalitas, saya dapat mengakses singleton dengan keyakinan penuh bahwa layanan sudah siap dan siap digunakan. Ini adalah kunci ketika ada utas berbeda yang memulai proses yang harus melalui mesin keadaan untuk dianggap siap.

Biasanya saya akan membungkus XxxServicekelas yang membungkus singleton di sekitar kelas Xxx. Singleton tidak ada di kelas Xxxsama sekali, itu dipisahkan menjadi kelas lain XxxService,. Ini karena Xxxdapat memiliki banyak contoh, meskipun tidak mungkin, tetapi kami masih ingin memiliki satu Xxxcontoh yang dapat diakses secara global pada setiap sistem. XxxServicememberikan pemisahan kekhawatiran yang bagus. Xxxtidak harus memberlakukan kebijakan tunggal, namun kita dapat menggunakan Xxxsebagai tunggal ketika kita perlu.

Sesuatu seperti:

//XxxService.h:
/**
 * Provide singleton wrapper for Xxx object. This wrapper
 * can be autogenerated so is not made part of the object.
 */

#include "Xxx/Xxx.h"


class XxxService
{
    public:
    /**
     * Return a Xxx object as a singleton. The double check
     * singleton algorithm is used. A 0 return means there was
     * an error. Developers should use this as the access point to
     * get the Xxx object.
     *
     * <PRE>
     * @@ #include "Xxx/XxxService.h"
     * @@ Xxx* xxx= XxxService::Singleton();
     * <PRE>
     */

     static Xxx*     Singleton();

     private:
         static Mutex  mProtection;
};


//XxxService.cpp:

#include "Xxx/XxxService.h"                   // class implemented
#include "LockGuard.h"     

// CLASS SCOPE
//
Mutex XxxService::mProtection;

Xxx* XxxService::Singleton()
{
    static Xxx* singleton;  // the variable holding the singleton

    // First check to see if the singleton has been created.
    //
    if (singleton == 0)
    {
        // Block all but the first creator.
        //
        LockGuard lock(mProtection);

        // Check again just in case someone had created it
        // while we were blocked.
        //
        if (singleton == 0)
        {
            // Create the singleton Xxx object. It's assigned
            // to a temporary so other accessors don't see
            // the singleton as created before it really is.
            //
            Xxx* inprocess_singleton= new Xxx;

            // Move the singleton to state online so we know that is has
            // been created and it ready for use.
            //
            if (inprocess_singleton->MoveOnline())
            {
                LOG(0, "XxxService:Service: FAIL MoveOnline");
                return 0;
            }

            // Wait until the module says it's in online state.
            //
            if (inprocess_singleton->WaitTil(Module::MODULE_STATE_ONLINE))
            {
                LOG(0, "XxxService:Service: FAIL move to online");
                return 0;
            }

            // The singleton is created successfully so assign it.
            //
            singleton= inprocess_singleton;


        }// still not created
    }// not created

    // Return the created singleton.
    //
    return singleton;

}// Singleton  
Todd Hoff
sumber
1

Pertanyaan pertama, apakah Anda menemukan banyak bug dalam aplikasi? mungkin lupa memperbarui cache, atau cache buruk atau sulit diubah? (Saya ingat sebuah aplikasi tidak akan mengubah ukuran kecuali jika Anda mengubah warnanya juga ... Anda dapat mengubah kembali warnanya dan mempertahankan ukurannya).

Apa yang akan Anda lakukan adalah memiliki kelas itu tetapi HAPUS SEMUA ANGGOTA STATIK. Ok ini bukan nessacary tapi saya merekomendasikannya. Benar-benar Anda baru saja menginisialisasi kelas seperti kelas normal dan LULUS pointer di. Jangan frigen katakan ClassIWant.APtr (). LetMeChange.ANYTHINGATALL (). Andhave_no_structure ()

Ini lebih banyak bekerja tetapi sebenarnya, itu kurang membingungkan. Beberapa tempat di mana Anda tidak boleh mengubah hal-hal yang sekarang tidak dapat Anda lakukan karena tidak lagi bersifat global. Semua kelas manajer saya adalah kelas reguler, anggap saja seperti itu.


sumber
1

IMO, contoh Anda terdengar oke. Saya menyarankan anjak keluar sebagai berikut: objek cache untuk masing-masing (dan di belakang masing-masing) objek data; objek cache dan objek pengakses db memiliki antarmuka yang sama. Ini memberikan kemampuan untuk menukar cache masuk dan keluar dari kode; plus itu memberikan rute ekspansi yang mudah.

Grafis:

DB
|
DB Accessor for OBJ A
| 
Cache for OBJ A
|
OBJ A Client requesting

Accessor dan cache DB dapat mewarisi dari objek yang sama atau tipe bebek menjadi tampak seperti objek yang sama, apa pun. Selama Anda bisa pasang / kompilasi / uji dan masih berfungsi.

Ini memisahkan hal-hal sehingga Anda dapat menambahkan cache baru tanpa harus masuk dan memodifikasi beberapa objek Uber-Cache. YMMV. IANAL. DLL

Paul Nathan
sumber
1

Agak terlambat ke pesta, tapi lagian.

Singleton adalah alat dalam kotak peralatan, sama seperti yang lainnya. Semoga Anda memiliki lebih banyak di kotak peralatan Anda daripada hanya satu palu.

Pertimbangkan ini:

public void DoSomething()
{
    MySingleton.Instance.Work();
}

vs.

public void DoSomething(MySingleton singleton)
{
    singleton.Work();
}
DoSomething(MySingleton.instance);

Kasus 1 mengarah ke kopling tinggi, dll; Cara ke-2 tidak memiliki masalah yang dijelaskan @Aaronaught, sejauh yang saya tahu. Ini semua tentang bagaimana Anda menggunakannya.

Evgeni
sumber
Tidak setuju. Meskipun cara kedua adalah "lebih baik" (coupling menurun), ia masih memiliki masalah yang diselesaikan oleh DI. Anda tidak boleh mengandalkan konsumen kelas Anda untuk memberikan implementasi layanan Anda - yang lebih baik dilakukan di konstruktor ketika kelas dibuat. Antarmuka Anda seharusnya hanya membutuhkan minimum dalam hal argumen. Ada juga peluang yang layak bahwa kelas Anda memerlukan satu instance untuk beroperasi - lagi, bergantung pada konsumen untuk menegakkan aturan ini berisiko dan tidak perlu.
AlexFoxGill
Kadang-kadang Di adalah kerja keras untuk tugas yang diberikan. Metode dalam kode sampel bisa menjadi konstruktor, tetapi tidak harus - tanpa melihat contoh konkretnya argumen yang diperdebatkan. Juga, DoSomething dapat mengambil ISomething, dan MySingleton dapat mengimplementasikan antarmuka itu - ok, itu tidak ada dalam sampel .. tapi itu hanya sampel.
Evgeni
1

Suruh setiap layar mengambil Manajer dalam konstruktor mereka.

Ketika Anda memulai aplikasi Anda, Anda membuat satu instance dari manajer dan menyebarkannya.

Ini disebut Inversion of Control, dan memungkinkan Anda untuk menukar controller ketika konfigurasi berubah dan dalam pengujian. Selain itu, Anda dapat menjalankan beberapa contoh aplikasi Anda atau bagian dari aplikasi Anda secara paralel (baik untuk pengujian!). Terakhir, manajer Anda akan mati dengan objeknya sendiri (kelas startup).

Jadi susun aplikasi Anda seperti pohon, di mana benda-benda di atas memiliki semua yang digunakan di bawahnya. Jangan menerapkan aplikasi seperti mesh, di mana semua orang mengenal semua orang dan menemukan satu sama lain melalui metode global.

Alexander Torstling
sumber