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:
Itu membuat kode lebih mudah dibaca; Anda dapat dengan jelas memahami dari antarmuka data apa yang bergantung pada kelas dependen.
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.
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 Cache
instance 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.