Alternatif untuk pola singleton

27

Saya telah membaca berbagai pendapat tentang pola singleton. Beberapa berpendapat bahwa itu harus dihindari dengan cara apa pun dan yang lain itu dapat berguna dalam situasi tertentu.

Satu situasi di mana saya menggunakan lajang adalah ketika saya membutuhkan sebuah pabrik (katakanlah objek f dari tipe F) untuk membuat objek dari kelas tertentu A. Pabrik dibuat sekali menggunakan beberapa parameter konfigurasi dan kemudian digunakan setiap kali objek dari tipe A dipakai. Jadi setiap bagian dari kode yang ingin instantiate A mengambil singleton f dan membuat instance baru, misalnya

F& f                   = F::instance();
boost::shared_ptr<A> a = f.createA();

Jadi secara umum skenario saya adalah itu

  1. Saya hanya perlu satu instance kelas baik untuk alasan optimasi (saya tidak perlu beberapa objek pabrik) atau untuk berbagi keadaan umum (mis. Pabrik tahu berapa banyak instance A yang masih dapat dibuat)
  2. Saya perlu cara untuk memiliki akses ke instance F ini di tempat kode yang berbeda.

Saya tidak tertarik pada diskusi apakah pola ini baik atau buruk, tetapi dengan asumsi saya ingin menghindari penggunaan singleton, pola apa lagi yang bisa saya gunakan?

Gagasan yang saya miliki adalah (1) untuk mendapatkan objek pabrik dari registri atau (2) untuk membuat pabrik di beberapa titik selama program dimulai dan kemudian melewati pabrik sebagai parameter.

Dalam solusi (1), registri itu sendiri adalah singleton, jadi saya baru saja menggeser masalah tidak menggunakan singleton dari pabrik ke registri.

Dalam kasus (2) saya memerlukan beberapa sumber awal (objek) dari mana objek pabrik berasal sehingga saya takut bahwa saya akan kembali ke singleton lain (objek yang menyediakan contoh pabrik saya). Dengan mengikuti kembali rantai lajang ini saya mungkin dapat mengurangi masalah menjadi satu lajang (seluruh aplikasi) dimana semua lajang lainnya dikelola secara langsung atau tidak langsung.

Apakah opsi terakhir ini (menggunakan satu singleton awal yang menciptakan semua objek unik lainnya dan menyuntikkan semua lajang lainnya di tempat yang tepat) menjadi solusi yang dapat diterima? Apakah ini solusi yang disarankan secara implisit ketika seseorang menyarankan untuk tidak menggunakan lajang, atau apa solusi lain, misalnya dalam contoh yang diilustrasikan di atas?

EDIT

Karena saya pikir pertanyaan saya telah disalahpahami oleh beberapa orang, berikut adalah beberapa informasi. Seperti yang dijelaskan misalnya di sini , kata singleton dapat menunjukkan (a) kelas dengan objek instance tunggal dan (b) pola desain yang digunakan untuk membuat dan mengakses objek tersebut.

Untuk memperjelas sesuatu, mari kita gunakan istilah objek unik untuk (a) dan pola tunggal untuk (b). Jadi, saya tahu apa pola singleton dan injeksi ketergantungan (BTW, akhir-akhir ini saya banyak menggunakan DI untuk menghapus instance dari pola singleton dari beberapa kode yang saya kerjakan).

Maksud saya adalah bahwa kecuali seluruh grafik objek adalah instantiated dari satu objek yang hidup pada tumpukan metode utama, akan selalu ada kebutuhan untuk mengakses beberapa objek unik melalui pola singleton.

Pertanyaan saya adalah apakah memiliki pembuatan grafik lengkap dan pengkabelan bergantung pada metode utama (misalnya melalui beberapa kerangka DI yang kuat yang tidak menggunakan pola itu sendiri) adalah satu-satunya solusi bebas pola tunggal .

Giorgio
sumber
8
Dependency Injection ...
Falcon
1
@ Falcon: Injeksi Ketergantungan ... apa? DI tidak melakukan apa pun untuk menyelesaikan masalah ini, meskipun itu sering memberi Anda cara yang mudah untuk menyembunyikannya.
pdr
@ Falcon maksudmu wadah IoC? Ini akan memungkinkan Anda untuk menyelesaikan dependensi dalam satu baris. Misalnya Dependency.Resolve <IFoo> (); atau Dependency.Resolve <IFoo> (). Doo sesuatu ();
CodeART
Ini pertanyaan yang cukup lama, dan sudah dijawab. Saya masih berpikir akan lebih baik untuk menautkan yang ini: stackoverflow.com/questions/162042/...
TheSilverBullet

Jawaban:

7

Opsi kedua Anda adalah cara yang baik untuk dilakukan - ini adalah semacam suntikan ketergantungan, yang merupakan pola yang digunakan untuk berbagi status di seluruh program Anda saat Anda ingin menghindari lajang dan variabel global.

Anda tidak dapat menyiasati kenyataan bahwa sesuatu harus membuat pabrik Anda. Jika sesuatu terjadi pada aplikasi, maka jadilah itu. Poin pentingnya adalah bahwa pabrik Anda seharusnya tidak peduli benda apa yang membuatnya, dan benda-benda yang menerima pabrik tidak boleh bergantung pada dari mana pabrik itu berasal. Jangan minta benda Anda mendapatkan pointer ke singleton aplikasi dan memintanya untuk pabrik; minta aplikasi Anda membuat pabrik dan memberikannya kepada benda-benda yang membutuhkannya.

Caleb
sumber
17

Tujuan seorang lajang adalah untuk menegakkan bahwa hanya satu contoh yang pernah ada dalam dunia tertentu. Ini berarti bahwa singleton berguna jika Anda memiliki alasan kuat untuk menegakkan perilaku singleton; dalam praktiknya, ini jarang terjadi, dan multi-pemrosesan dan multi-threading jelas mengaburkan makna 'unik' - apakah ini satu contoh per mesin, per proses, per utas, per permintaan? Dan apakah implementasi tunggal Anda merawat kondisi ras?

Alih-alih singleton, saya lebih suka menggunakan salah satu dari:

  • contoh-contoh lokal berumur pendek, misalnya untuk sebuah pabrik: kelas-kelas pabrik tipikal memiliki jumlah negara yang minimal, jika ada, dan tidak ada alasan nyata untuk mempertahankannya setelah mereka memenuhi tujuannya; overhead menciptakan dan menghapus kelas tidak perlu dikhawatirkan di 99% dari semua skenario dunia nyata
  • memberikan contoh, misalnya untuk manajer sumber daya: ini harus berumur panjang, karena memuat sumber daya mahal dan Anda ingin menyimpannya dalam ingatan, tetapi sama sekali tidak ada alasan untuk mencegah pembuatan instance lebih lanjut - siapa tahu, mungkin masuk akal untuk memiliki manajer sumber daya kedua beberapa bulan ke depan ...

Alasannya adalah bahwa singleton adalah negara global yang menyamar, yang berarti itu memperkenalkan tingkat tinggi dari penggabungan seluruh aplikasi - bagian mana pun dari kode Anda dapat mengambil instance singleton dari mana saja dengan upaya minimal. Jika Anda menggunakan benda-benda lokal atau memberikan contoh, Anda memiliki kontrol lebih besar, dan Anda dapat menjaga cakupan Anda kecil dan dependensi Anda sempit.

tammmer
sumber
3
Ini adalah jawaban yang sangat bagus tetapi saya tidak benar-benar bertanya mengapa saya harus / tidak harus menggunakan pola tunggal. Saya tahu masalah yang bisa dihadapi seseorang dengan negara global. Lagi pula, topik pertanyaannya adalah bagaimana memiliki objek unik dan implementasi bebas pola tunggal.
Giorgio
3

Kebanyakan orang (termasuk Anda) benar-benar salah memahami apa sebenarnya pola Singleton. Pola Singleton hanya berarti bahwa ada satu instance dari sebuah kelas dan ada beberapa mekanisme untuk kode di seluruh aplikasi untuk mendapatkan referensi ke instance itu.

Dalam buku GoF, metode pabrik statis yang mengembalikan referensi ke bidang statis hanyalah contoh bagaimana mekanisme itu mungkin terlihat, dan metode yang memiliki kelemahan parah. Sayangnya, semua orang dan anjing mereka menggunakan mekanisme itu dan mengira itu adalah tentang Singleton.

Alternatif yang Anda kutip sebenarnya juga lajang, hanya dengan mekanisme berbeda untuk mendapatkan referensi. (2) jelas mengakibatkan terlalu banyak melewati, kecuali Anda memerlukan referensi hanya di beberapa tempat di dekat akar tumpukan panggilan. (1) terdengar seperti kerangka kerja injeksi ketergantungan kasar - jadi mengapa tidak menggunakannya?

Keuntungannya adalah bahwa kerangka kerja DI yang ada fleksibel, kuat dan teruji dengan baik. Mereka dapat melakukan lebih dari sekedar mengelola Lajang. Namun, untuk sepenuhnya menggunakan kemampuan mereka, mereka bekerja paling baik jika Anda menyusun aplikasi Anda dengan cara tertentu, yang tidak selalu mungkin: idealnya, ada objek pusat yang diperoleh melalui kerangka kerja DI dan memiliki semua dependensi mereka yang terisi secara transitif dan kemudian dieksekusi.

Sunting: Pada akhirnya, semuanya tergantung pada metode utama. Tapi Anda benar: satu-satunya cara untuk menghindari penggunaan global / statis sepenuhnya adalah dengan mengatur semuanya dari metode utama. Perhatikan bahwa DI paling populer di lingkungan server di mana metode utama adalah buram dan mengatur server dan kode aplikasi pada dasarnya terdiri dari panggilan balik yang dipakai dan dipanggil oleh kode server. Tetapi sebagian besar kerangka kerja DI juga memungkinkan akses langsung ke registri pusat mereka, misalnya Spring's ApplicationContext, untuk "kasus khusus".

Jadi pada dasarnya, hal terbaik yang telah dikemukakan orang sejauh ini adalah kombinasi cerdas dari dua alternatif yang Anda sebutkan.

Michael Borgwardt
sumber
Apakah maksud Anda bahwa injeksi ketergantungan ide dasar pada dasarnya adalah pendekatan pertama yang saya buat di atas (misalnya menggunakan registri), akhirnya gagasan dasarnya?
Giorgio
@Iorgio: DI selalu menyertakan registry seperti itu, tetapi poin utama yang membuatnya lebih baik adalah bahwa alih-alih meminta registri di mana pun Anda membutuhkan objek, Anda memiliki objek pusat yang secara transitif terisi, seperti yang saya tulis di atas.
Michael Borgwardt
@Iorgio: Lebih mudah dipahami dengan contoh konkret: katakanlah Anda memiliki aplikasi Java EE. Logika front-end Anda adalah dalam metode aksi dari JSF Managed Bean. Ketika pengguna menekan tombol, wadah JSF membuat instance dari Managed Bean dan memanggil metode tindakan. Tetapi sebelum itu, komponen DI melihat bidang kelas Managed Bean, dan mereka yang memiliki anotasi tertentu diisi dengan referensi ke objek Layanan sesuai konfigurasi DI, dan objek Layanan tersebut memiliki hal yang sama terjadi pada mereka . Metode tindakan kemudian dapat memanggil layanan.
Michael Borgwardt
Beberapa orang (termasuk Anda) tidak membaca pertanyaan dengan hati-hati dan salah paham tentang apa yang sebenarnya ditanyakan.
Giorgio
1
@Iorgio: tidak ada dalam pertanyaan awal Anda yang menunjukkan bahwa Anda sudah terbiasa dengan injeksi ketergantungan. Dan lihat jawaban yang diperbarui.
Michael Borgwardt
3

Ada beberapa alternatif:

Injeksi Ketergantungan

Setiap objek memiliki dependensi yang diteruskan kepadanya ketika objek itu dibuat. Biasanya kerangka kerja atau sejumlah kecil kelas pabrik bertanggung jawab untuk membuat dan menghubungkan objek

Daftar Layanan

Setiap objek melewati objek registri layanan. Ini menyediakan metode untuk meminta berbagai objek yang berbeda yang menyediakan layanan yang berbeda. Kerangka kerja Android menggunakan pola ini

Manajer Root

Ada satu objek yang merupakan akar dari grafik objek. Dengan mengikuti tautan dari objek ini, objek lain akhirnya dapat ditemukan. Kode cenderung terlihat seperti:

GetSomeManager()->GetRootManager()->GetAnotherManager()->DoActualThingICareAbout()
Winston Ewert
sumber
Selain mengejek, keuntungan apa yang DI miliki dibandingkan menggunakan singleton? Ini adalah pertanyaan yang telah menyadap saya sejak lama. Sedangkan untuk registry dan root manager, bukankah mereka diimplementasikan sebagai singleton atau via DI? (Faktanya, wadah IoC adalah registri, kan?)
Konrad Rudolph
1
@KonradRudolph, dengan DI dependensi eksplisit, dan objek tidak membuat asumsi tentang bagaimana sumber daya yang diberikan diberikan. Dengan lajang, dependensi bersifat implisit dan setiap penggunaan tunggal membuat asumsi bahwa hanya akan ada satu objek seperti itu.
Winston Ewert
@KonradRudolph, apakah metode lain diimplementasikan menggunakan Singletons atau DI? Ya dan Tidak. Pada akhirnya, Anda harus meneruskan objek atau menyimpan objek dalam kondisi global. Tetapi ada perbedaan halus dalam cara Anda melakukan ini. Tiga versi yang disebutkan di atas menghindari penggunaan negara global; tetapi cara di mana kode akhir mendapatkan referensi berbeda.
Winston Ewert
Penggunaan lajang mengasumsikan tidak ada objek tunggal jika singleton mengimplementasikan antarmuka (atau bahkan jika tidak) dan Anda melewatkan instance singleton ke metode, alih-alih bertanya Singleton::instancedi mana-mana dalam kode klien.
Konrad Rudolph
@KonradRudolph, maka Anda benar-benar menggunakan beberapa pendekatan Singleton / DI hybrid. Sisi kemurnian saya berpikir Anda harus melakukan pendekatan DI murni. Namun, sebagian besar masalah dengan singleton tidak benar-benar berlaku di sana.
Winston Ewert
1

Jika kebutuhan Anda dari singleton dapat dirubah menjadi satu fungsi, mengapa tidak menggunakan fungsi pabrik yang sederhana? Fungsi global (mungkin metode statis kelas F dalam contoh Anda) pada dasarnya adalah lajang, dengan keunikan ditegakkan oleh kompiler dan penghubung.

class Factory
{
public:
    static Object* createObject(...);
};

Object* obj = Factory::createObject(...);

Memang, ini rusak ketika operasi singleton tidak dapat direduksi menjadi panggilan fungsi tunggal, meskipun mungkin satu set kecil fungsi terkait dapat membantu Anda.

Semua yang dikatakan, item 1 dan 2 dalam pertanyaan Anda menjelaskan bahwa Anda benar-benar hanya menginginkan satu dari sesuatu. Tergantung pada definisi Anda tentang pola singleton, Anda sudah menggunakannya atau sangat dekat. Saya tidak berpikir Anda dapat memiliki salah satu dari sesuatu tanpa menjadi tunggal atau setidaknya sangat dekat dengan satu. Itu terlalu dekat dengan makna singleton.

Seperti yang Anda sarankan, pada titik tertentu Anda harus memiliki sesuatu, jadi mungkin masalahnya adalah tidak memiliki satu contoh pun dari sesuatu, tetapi mengambil langkah-langkah untuk mencegah (atau setidaknya mencegah atau meminimalkan) penyalahgunaannya. Memindahkan status keluar dari "singleton" ke dalam parameter sebanyak mungkin adalah awal yang baik. Mempersempit antarmuka ke singleton juga membantu, karena ada lebih sedikit peluang untuk penyalahgunaan seperti itu. Terkadang Anda hanya perlu menyedotnya dan membuat singleton sangat kuat, seperti heap atau filesystem.

Randall Cook
sumber
4
Secara pribadi saya melihat ini hanya sebagai implementasi yang berbeda dari pola tunggal. Dalam hal ini instance singleton adalah kelas itu sendiri. Khususnya: ia memiliki semua kelemahan yang sama dengan singleton "nyata".
Joachim Sauer
@Rallall Cook: Saya pikir jawaban Anda menangkap maksud saya. Saya membaca sangat sering bahwa singleton sangat buruk dan harus dihindari. Saya menyetujui hal ini tetapi di sisi lain saya pikir secara teknis tidak mungkin untuk selalu menghindarinya. Ketika Anda membutuhkan "salah satu dari sesuatu" Anda harus membuatnya di suatu tempat dan mengaksesnya dengan cara tertentu.
Giorgio
1

Jika Anda menggunakan bahasa multiparadigma seperti C ++ atau Python, salah satu alternatif untuk kelas singleton adalah seperangkat fungsi / variabel yang dibungkus dalam namespace.

Secara konseptual, file C ++ dengan variabel global gratis, fungsi global gratis, dan variabel statis yang digunakan untuk menyembunyikan informasi, semuanya dibungkus dalam namespace, memberi Anda efek yang hampir sama dengan "kelas" tunggal.

Itu hanya rusak jika Anda ingin warisan. Saya telah melihat banyak lajang yang akan lebih baik dari sini.

Gort the Robot
sumber
0

Enkapsulasi lajang

Skenario kasus. Aplikasi Anda Berorientasi Objek, dan membutuhkan 3 atau 4 lajang khusus, bahkan jika Anda memiliki lebih banyak kelas.

Sebelum Contoh (C ++ suka pseudocode):

// network info
class Session {
  // ...
};

// current user connection info
class CurrentUser {
  // ...
};

// configuration upon user
class UserConfiguration {
  // ...
};

// configuration upon P.C.,
// example: monitor resolution
class TerminalConfiguration {
  // ...
};

Salah satu caranya adalah dengan menghapus sebagian lajang (global), dengan merangkum semuanya menjadi lajang yang unik, yang dimiliki sebagai anggota, lajang lainnya.

Ini, cara, alih-alih "beberapa lajang, pendekatan, versus, tidak ada lajang sama sekali, pendekatan", kita memiliki "merangkum semua lajang menjadi satu, pendekatan".

Setelah Contoh (C ++ seperti pseudocode):

// network info
class NetworkInfoClass {
  // ...
};

// current user connection info
class CurrentUserClass {
  // ...
};

// configuration upon user
// favorite options menues
class UserConfigurationClass {
  // ...
};

// configuration upon P.C.,
// example: monitor resolution
class TerminalConfigurationClass {
  // ...
};

// this class is instantiated as a singleton
class SessionClass {
  public: NetworkInfoClass NetworkInfo;
  public: CurrentUserClass CurrentUser;
  public: UserConfigurationClass UserConfiguration;
  public: TerminalConfigurationClass TerminalConfiguration;

  public: static SessionClass Instance();
};

Harap perhatikan, bahwa contohnya lebih seperti pseudocode, dan abaikan bug minor, atau kesalahan sintaks, dan pertimbangkan solusi untuk pertanyaan tersebut.

Ada juga hal lain yang perlu dipertimbangkan, karena, bahasa pemrograman yang digunakan, dapat mempengaruhi cara mengimplementasikan implementasi singleton atau non singleton Anda.

Tepuk tangan.

umlcat
sumber
0

Persyaratan asli Anda:

Jadi secara umum skenario saya adalah itu

  1. Saya hanya perlu satu instance kelas baik untuk alasan optimasi (saya tidak perlu> beberapa objek pabrik) atau untuk berbagi keadaan umum (mis. Pabrik tahu berapa> instance dari A yang masih dapat dibuat)
  2. Saya perlu cara untuk memiliki akses ke instance F ini di tempat kode yang berbeda.

Jangan sejajar dengan definisi singleton (dan apa yang lebih Anda rujuk nanti). Dari GoF (versi saya adalah 1995) halaman 127:

Pastikan kelas hanya memiliki satu instance, dan memberikan titik akses global untuk itu.

Jika Anda hanya membutuhkan satu contoh, itu tidak menghalangi Anda untuk membuat lebih banyak.

Jika Anda ingin contoh tunggal yang dapat diakses secara global, buat contoh tunggal yang dapat diakses secara global . Tidak perlu memiliki pola yang dinamai untuk semua yang Anda lakukan. Dan ya, satu contoh yang dapat diakses secara global biasanya buruk. Memberi mereka nama tidak membuat mereka tidak terlalu buruk .

Untuk menghindari aksesibilitas global, pendekatan umum 'membuat sebuah contoh dan meneruskannya' atau 'membuat pemilik dua benda saling menempel' bekerja dengan baik.

Telastyn
sumber
0

Bagaimana kalau menggunakan wadah IOC? Dengan beberapa pemikiran Anda dapat berakhir dengan sesuatu di baris:

Dependency.Resolve<IFoo>(); 

Anda harus menentukan implementasi mana yang IFooakan digunakan pada startup, tetapi ini adalah konfigurasi nonaktif yang nantinya dapat Anda gantikan dengan mudah. Seumur hidup contoh diselesaikan biasanya dapat dikendalikan oleh wadah IoC.

Metode singleton void statis dapat diganti dengan:

Dependency.Resolve<IFoo>().DoSomething();

Metode pengambil singleton statis dapat diganti dengan:

var result = Dependency.Resolve<IFoo>().GetFoo();

Contoh relevan dengan .NET, tapi saya yakin sangat mirip dapat dicapai dalam bahasa lain.

CodeART
sumber