Tentang pola desain: Kapan saya harus menggunakan singleton?


448

Variabel global yang dimuliakan - menjadi kelas global yang dimuliakan. Ada yang mengatakan melanggar desain berorientasi objek.

Berikan saya skenario, selain penebang tua yang baik di mana masuk akal untuk menggunakan singleton.


4
Sejak belajar erlang, saya lebih suka pendekatan itu, yaitu immutability dan message passing.
Setori

209
Apa yang tidak konstruktif tentang pertanyaan ini? Saya melihat jawaban konstruktif di bawah ini.
mk12

3
Kerangka kerja injeksi ketergantungan adalah singleton yang sangat kompleks yang memberikan objek….
Ian Ringrose

1
Singleton dapat digunakan sebagai objek manajer antara instance dari objek lain, sehingga hanya ada satu instance dari singleton di mana setiap instance lain harus berkomunikasi melalui instance singleton.
Levent Divilioglu

Saya punya pertanyaan sampingan: implementasi Singleton juga dapat diimplementasikan menggunakan kelas "statis" (dengan metode "pabrik" / "init") - tanpa benar-benar membuat turunan dari kelas (Anda dapat mengatakan bahwa kelas statis adalah jenis implementasi Singleton, tapi ...) - mengapa kita harus menggunakan Singleton yang sebenarnya (instance kelas tunggal yang memastikan single-nya) alih-alih kelas statis? Satu-satunya alasan yang dapat saya pikirkan adalah mungkin untuk "semantik", tetapi bahkan dalam arti itu, Singleton use case tidak benar-benar memerlukan hubungan "class-> instance" dengan definisi ... jadi ... mengapa?
Yuval A.

Jawaban:


358

Dalam pencarian saya akan kebenaran, saya menemukan bahwa sebenarnya ada sangat sedikit alasan "dapat diterima" untuk menggunakan Singleton.

Salah satu alasan yang cenderung muncul berulang-ulang di internet adalah karena kelas "logging" (yang Anda sebutkan). Dalam hal ini, Singleton dapat digunakan sebagai pengganti satu instance kelas karena kelas logging biasanya perlu digunakan berulang-ulang untuk mual oleh setiap kelas dalam suatu proyek. Jika setiap kelas menggunakan kelas logging ini, injeksi ketergantungan menjadi rumit.

Logging adalah contoh spesifik dari Singleton yang "dapat diterima" karena itu tidak mempengaruhi eksekusi kode Anda. Nonaktifkan logging, eksekusi kode tetap sama. Aktifkan, sama saja. Misko memasukkannya dengan cara berikut dalam Root Cause of Singletons , "Informasi di sini mengalir satu arah: Dari aplikasi Anda ke dalam logger. Meskipun logger adalah keadaan global, karena tidak ada informasi mengalir dari logger ke dalam aplikasi Anda, logger dapat diterima."

Saya yakin ada alasan sah lainnya juga. Alex Miller, dalam " Patterns I Hate ", pembicaraan tentang pencari layanan dan UI sisi klien juga kemungkinan pilihan yang "dapat diterima".

Baca lebih lanjut di Singleton, aku mencintaimu, tetapi kau menjatuhkanku.


3
@ArneMertz Saya kira ini dia.
Attacktive

1
Mengapa Anda tidak bisa menggunakan objek global saja? Mengapa itu harus lajang?
Sepatu

1
Saya pikir metode statis untuk util logging?
Skynet

1
Lajang sangat baik ketika Anda perlu mengelola sumber daya. Misalnya, koneksi Http. Anda tidak ingin membuat 1 juta klien http untuk satu klien, itu gila sia-sia dan lambat. Jadi singleton dengan koneksi yang dikumpulkan klien http akan jauh lebih cepat dan ramah sumber daya.
Cogman

3
Saya tahu ini adalah pertanyaan lama, dan informasi dalam jawaban ini sangat bagus. Namun, saya mengalami kesulitan memahami mengapa ini adalah jawaban yang diterima ketika OP dengan jelas menentukan: "Beri saya skenario, selain penebang tua yang baik di mana masuk akal untuk menggunakan singleton."
Francisco C.

124

Kandidat Singleton harus memenuhi tiga persyaratan:

  • mengontrol akses bersamaan ke sumber daya bersama.
  • akses ke sumber daya akan diminta dari beberapa bagian sistem yang berbeda.
  • hanya ada satu objek.

Jika Singleton yang Anda usulkan hanya memiliki satu atau dua persyaratan ini, pendesainan ulang hampir selalu merupakan pilihan yang tepat.

Misalnya, spooler printer tidak mungkin dipanggil dari lebih dari satu tempat (menu Print), sehingga Anda dapat menggunakan mutex untuk menyelesaikan masalah akses bersamaan.

Logger sederhana adalah contoh yang paling jelas dari Singleton yang mungkin valid, tetapi ini dapat berubah dengan skema logging yang lebih kompleks.


3
Saya tidak setuju dengan poin 2. Poin 3 tidak benar-benar alasan (hanya karena Anda bisa itu tidak berarti Anda harus) dan 1 adalah poin yang baik tetapi saya masih tidak melihat kegunaannya. Katakanlah sumber daya bersama adalah disk drive atau cache db. Anda dapat menambahkan drive lain atau memiliki cache db yang berfokus pada hal lain (seperti cache untuk tabel khusus untuk satu utas dengan yang lain menjadi tujuan yang lebih umum).

15
Saya pikir Anda melewatkan kata "kandidat". Kandidat Singleton harus memenuhi ketiga persyaratan; hanya karena sesuatu memenuhi persyaratan, tidak berarti harus Singleton. Mungkin ada faktor desain lain :)
metao

45

Membaca file konfigurasi yang seharusnya hanya dibaca pada waktu startup dan merangkumnya dalam Singleton.


8
Mirip dengan Properties.Settings.Defaultdi .NET.
Nick Bedford

9
@ Paul, The "no-singleton camp" akan menyatakan bahwa objek konfigurasi hanya harus dilewatkan ke fungsi yang membutuhkannya, alih-alih membuatnya dapat diakses secara global (alias singleton).
Pacerier

2
Tidak setuju. Jika konfigurasi dipindahkan ke database, semuanya kacau. Jika jalur ke konfigurasi tergantung pada apa pun di luar singleton itu, hal-hal ini harus statis juga.
tanggal

3
@ PaulCroarkin Bisakah Anda memperluas ini dan menjelaskan bagaimana ini bermanfaat?
AlexG

1
@ rr- jika konfigurasi berpindah ke database, masih dapat dienkapsulasi dalam objek konfigurasi yang akan diteruskan ke fungsi yang membutuhkannya. (PS Saya tidak di kamp "no-singleton").
Will Sheppard

36

Anda menggunakan singleton ketika Anda perlu mengelola sumber daya bersama. Misalnya spooler printer. Aplikasi Anda hanya boleh memiliki satu instance spooler untuk menghindari permintaan yang saling bertentangan untuk sumber daya yang sama.

Atau koneksi database atau file manager dll.


30
Saya pernah mendengar contoh spooler printer ini dan saya pikir itu agak timpang. Siapa bilang saya tidak bisa memiliki lebih dari satu spooler? Apa sih yang disebut spooler printer? Bagaimana jika saya memiliki berbagai jenis printer yang tidak dapat bertentangan atau menggunakan driver yang berbeda?
1800 INFORMASI

6
Ini hanya contoh ... untuk situasi apa pun yang digunakan orang sebagai contoh, Anda akan dapat menemukan desain alternatif yang membuat contoh tidak berguna. Mari kita berpura-pura bahwa pengumpul informasi mengelola satu sumber daya tunggal yang dibagi oleh beberapa komponen. Berhasil.
Vincent Ramdhanie

2
Ini adalah contoh klasik untuk Gang of Four. Saya pikir jawaban dengan nyata mencoba kasus penggunaan akan lebih berguna. Maksud saya situasi di mana Anda benar-benar merasakan Singleton adalah solusi terbaik.
Andrei Vajna II

Sumber daya bersama menurut pendapat saya adalah contoh yang terlalu luas. Bagaimana Anda menguji apakah objek yang menggunakan spooler cetak berfungsi dengan benar di hadapan spooler yang tidak berfungsi saat Anda tidak dapat menyuntikkan implementasi 'spooler' yang tidak berfungsi? meskipun singkat dan tidak informatif, jawaban yang diterima adalah pendekatan yang jauh lebih aman dalam buku saya
Rune FS

Apa yang dimaksud dengan spooler printer?
Ray Tanpa Cinta

23

Baca hanya orang lajang yang menyimpan beberapa keadaan global (bahasa pengguna, bantuan filepath, jalur aplikasi) masuk akal. Berhati-hatilah menggunakan lajang untuk mengendalikan logika bisnis - tunggal hampir selalu berakhir dengan banyak


4
Bahasa pengguna hanya dapat tunggal dengan asumsi bahwa hanya satu pengguna yang dapat menggunakan sistem.
Samuel Åslund

... dan satu pengguna itu hanya berbicara satu bahasa.
Spektrum

17

Mengelola koneksi (atau kumpulan koneksi) ke database.

Saya akan menggunakannya juga untuk mengambil dan menyimpan informasi pada file konfigurasi eksternal.


2
Bukankah generator koneksi database menjadi contoh Pabrik?
Ken

3
@ Ben, Anda ingin pabrik itu menjadi singleton di hampir semua kasus.
Chris Marisic

2
@Federico, The "no-singleton camp" akan menyatakan bahwa koneksi basis data ini hanya dapat diteruskan ke fungsi yang membutuhkannya, alih-alih membuatnya dapat diakses secara global (alias singleton).
Pacerier

3
Anda tidak benar-benar membutuhkan singleton untuk ini. Itu bisa disuntikkan.
Nestor Ledon

11

Salah satu cara Anda menggunakan singleton adalah untuk meliput contoh di mana harus ada "broker" tunggal yang mengontrol akses ke sumber daya. Lajang baik dalam logger karena mereka broker akses ke, katakanlah, file, yang hanya dapat ditulis secara eksklusif. Untuk sesuatu seperti pencatatan, mereka menyediakan cara untuk mengabstraksi penulisan ke sesuatu seperti file log - Anda bisa membungkus mekanisme caching ke singleton Anda, dll ...

Juga pikirkan situasi di mana Anda memiliki aplikasi dengan banyak jendela / utas / etc, tetapi yang membutuhkan satu titik komunikasi. Saya pernah menggunakan satu untuk mengontrol pekerjaan yang saya ingin aplikasi saya luncurkan. Singleton bertanggung jawab untuk membuat serial pekerjaan dan menampilkan status mereka ke bagian lain dari program yang tertarik. Dalam skenario semacam ini, Anda dapat melihat singleton sebagai semacam "server" yang berjalan di dalam aplikasi Anda ... HTH


3
Penebang paling sering lajang sehingga objek logging tidak harus diedarkan. Setiap implementasi yang layak dari aliran log akan memastikan bahwa penulisan bersamaan tidak mungkin, apakah itu Singleton atau tidak.
metao

10

Satu singleton harus digunakan ketika mengelola akses ke sumber daya yang dibagikan oleh seluruh aplikasi, dan akan merusak jika berpotensi memiliki banyak instance dari kelas yang sama. Memastikan bahwa akses ke sumber daya bersama thread aman adalah salah satu contoh yang sangat baik di mana pola semacam ini sangat penting.

Saat menggunakan lajang, Anda harus memastikan bahwa Anda tidak sengaja menyembunyikan dependensi. Idealnya, lajang (seperti kebanyakan variabel statis dalam suatu aplikasi) diatur selama eksekusi kode inisialisasi Anda untuk aplikasi (static void Main () untuk C # executable, static void main () untuk executable java) dan kemudian diteruskan ke semua kelas lain yang dipakai yang membutuhkannya. Ini membantu Anda mempertahankan testability.


8

Saya pikir penggunaan tunggal dapat dianggap sama dengan hubungan banyak-ke-satu dalam basis data. Jika Anda memiliki banyak bagian berbeda dari kode Anda yang perlu bekerja dengan satu instance objek, di situlah masuk akal untuk menggunakan lajang.


6

Contoh praktis dari singleton dapat ditemukan di Test :: Builder , kelas yang mendukung hampir semua modul pengujian Perl modern. Test :: Builder singleton menyimpan dan memperantarai keadaan dan sejarah proses pengujian (hasil tes historis, menghitung jumlah tes yang dijalankan) serta hal-hal seperti ke mana hasil tes akan berjalan. Ini semua diperlukan untuk mengoordinasikan beberapa modul pengujian, yang ditulis oleh penulis yang berbeda, untuk bekerja bersama dalam satu skrip pengujian.

Sejarah Test :: singleton Builder bersifat mendidik. Memanggil new()selalu memberi Anda objek yang sama. Pertama, semua data disimpan sebagai variabel kelas dengan tidak ada di objek itu sendiri. Ini bekerja sampai saya ingin menguji Test :: Builder dengan sendirinya. Kemudian saya membutuhkan dua objek Test :: Builder, satu setup sebagai dummy, untuk menangkap dan menguji perilaku dan outputnya, dan satu untuk menjadi objek tes yang sebenarnya. Pada titik itu Test :: Builder di refactored menjadi objek nyata. Objek tunggal disimpan sebagai data kelas, dan new()akan selalu mengembalikannya. create()telah ditambahkan untuk membuat objek segar dan mengaktifkan pengujian.

Saat ini, pengguna ingin mengubah beberapa perilaku Test :: Builder dalam modul mereka sendiri, tetapi membiarkan yang lain sendirian, sementara riwayat tes tetap sama di semua modul pengujian. Apa yang terjadi sekarang adalah Uji monolitik :: objek Builder sedang dipecah menjadi potongan-potongan kecil (sejarah, output, format ...) dengan contoh Test :: Builder mengumpulkan mereka bersama-sama. Sekarang Uji :: Builder tidak lagi harus tunggal. Komponen-komponennya, seperti sejarah, bisa jadi. Ini mendorong kebutuhan yang tidak fleksibel dari seorang singleton ke tingkat yang lebih rendah. Ini memberi lebih banyak fleksibilitas kepada pengguna untuk mencampur dan mencocokkan bagian. Objek singleton yang lebih kecil sekarang hanya dapat menyimpan data, dengan objeknya yang berisi memutuskan bagaimana menggunakannya. Bahkan memungkinkan kelas non-Test :: Builder untuk bermain bersama dengan menggunakan Test :: Builder history dan output singletons.

Tampaknya ada dorongan dan tarik antara koordinasi data dan fleksibilitas perilaku yang dapat dikurangi dengan menempatkan singleton sekitar hanya data yang dibagikan dengan jumlah perilaku sekecil mungkin untuk memastikan integritas data.


5

Ketika Anda memuat objek Properties konfigurasi, baik dari database atau file, itu membantu untuk memilikinya sebagai singleton; tidak ada alasan untuk terus membaca kembali data statis yang tidak akan berubah saat server sedang berjalan.


2
Mengapa Anda tidak hanya memuat data sekali dan melewatkan objek konfigurasi yang diperlukan?
lagweezle

ada apa dengan berkeliling ??? Jika saya harus melewati setiap objek yang saya butuhkan, saya akan memiliki konstruktor dengan 20 argumen ...
Enerccio

@Enerccio Jika Anda memiliki objek yang bergantung pada 20 lainnya tanpa enkapsulasi, Anda sudah memiliki masalah desain utama.
spektrum

@spectras Do I? Jika saya menerapkan dialog gui, saya perlu: repositori, pelokalan, data sesi, data aplikasi, induk widget, data klien, pengelola izin, dan mungkin lebih. Tentu, Anda dapat mengumpulkan beberapa, tetapi mengapa? Secara pribadi saya menggunakan pegas dan aspek untuk hanya autowire semua dependensi ini ke dalam kelas widget dan yang memisahkan segalanya.
Enerccio

Jika Anda memiliki kondisi sebanyak itu, Anda dapat mempertimbangkan untuk menerapkan fasad, memberikan pandangan tentang aspek yang relevan dengan konteks tertentu. Mengapa? Karena itu akan memungkinkan desain yang bersih tanpa antipatterns singleton atau 29-argumen konstruktor. Sebenarnya dialog gui Anda mengakses semua hal itu berteriak "pelanggaran prinsip tanggung jawab tunggal".
Spektrum

3

Seperti yang dikatakan semua orang, sumber daya bersama - khususnya sesuatu yang tidak dapat menangani akses bersamaan.

Salah satu contoh spesifik yang saya lihat, adalah Lucene Search Index Writer.


1
Namun, IndexWriter bukan singleton ...
Mark

3

Anda dapat menggunakan Singleton saat menerapkan pola Negara (dengan cara yang ditunjukkan dalam buku GoF). Ini karena kelas Negara konkret tidak memiliki negara mereka sendiri, dan melakukan tindakan mereka dalam konteks kelas konteks.

Anda juga dapat membuat Pabrik Abstrak menjadi singleton.


Ini adalah kasus yang saya hadapi sekarang dalam sebuah proyek. Saya menggunakan pola keadaan untuk menghapus kode kondisional berulang dari metode konteks. Negara bagian tidak memiliki variabel turunannya sendiri. Namun, saya berada di pagar sehubungan dengan apakah saya harus membuat mereka lajang. Setiap kali keadaan berganti instance baru dipakai. Ini memang tampak boros karena tidak ada cara instance dapat berbeda dari yang lain, (karena tidak ada variabel instance). Saya mencoba mencari tahu mengapa saya tidak boleh menggunakannya.
kiwicomb123

1
@ kiwicomb123 Cobalah untuk membuat Anda setState()bertanggung jawab untuk memutuskan kebijakan pembuatan negara. Ini membantu jika bahasa pemrograman Anda mendukung templat atau generik. Alih-alih Singleton, Anda bisa menggunakan pola Monostate , di mana instantiating objek negara akhirnya menggunakan kembali objek keadaan global / statis yang sama. Sintaks untuk mengubah status bisa tetap tidak berubah, karena pengguna Anda tidak perlu sadar bahwa keadaan yang dipakai adalah Monostate.
Emile Cormier

Oke jadi di negara saya, saya hanya bisa membuat semua metode statis, jadi setiap kali instance baru dibuat tidak memiliki overhead yang sama? Saya agak bingung, saya perlu membaca tentang pola Monostate.
kiwicomb123

@ kiwicomb123 Tidak, Monostate bukan tentang membuat semua anggota statis. Lebih baik Anda membacanya, lalu periksa SO untuk pertanyaan dan jawaban terkait.
Emile Cormier

Saya merasa ini harus memiliki lebih banyak suara. Pabrik abstrak cukup umum dan karena pabrik tidak memiliki kewarganegaraan, stabil dalam keadaan tanpa kewarganegaraan, dan tidak dapat diimplementasikan dengan metode statis (di Jawa) yang tidak diganti, penggunaan singleton harus baik-baik saja.
DPM

3

Sumber daya bersama. Terutama di PHP, kelas basis data, kelas templat, dan kelas depot variabel global. Semua harus dibagikan oleh semua modul / kelas yang digunakan di seluruh kode.

Ini adalah penggunaan objek yang benar -> kelas template berisi templat halaman yang sedang dibangun, dan itu akan dibentuk, ditambahkan, diubah oleh modul yang ditambahkan ke output halaman. Itu harus disimpan sebagai satu contoh sehingga ini bisa terjadi, dan hal yang sama berlaku untuk database. Dengan singleton basis data bersama, semua kelas modul bisa mendapatkan akses ke pertanyaan dan mendapatkannya tanpa harus memutarnya kembali.

Depot variabel global tunggal memberikan Anda depot variabel global, andal, dan mudah digunakan. Ini merapikan kode Anda banyak. Bayangkan memiliki semua nilai konfigurasi dalam array dalam singleton seperti:

$gb->config['hostname']

atau memiliki semua nilai bahasa dalam array seperti:

$gb->lang['ENTER_USER']

Di akhir menjalankan kode untuk halaman, Anda mendapatkan, katakanlah, sekarang sudah matang:

$template

Singleton, $gbsingleton yang memiliki larik bahasa untuk menggantikannya, dan semua output dimuat dan siap. Anda cukup menggantinya ke dalam kunci yang sekarang hadir dalam nilai halaman objek templat matang, dan kemudian menyajikannya kepada pengguna.

Keuntungan besar dari ini adalah Anda dapat melakukan SETIAP pengolahan pasca Anda suka pada apa pun. Anda dapat menyalurkan semua nilai bahasa ke google translate, atau layanan terjemahan lain dan mendapatkannya kembali, dan menggantinya ke tempat mereka, diterjemahkan, misalnya. atau, Anda dapat mengganti struktur halaman, atau, string konten, seperti yang Anda inginkan.


21
Anda mungkin ingin memecah jawaban Anda menjadi beberapa paragraf dan memblokir segmen kode untuk dibaca.
Justin

1

Akan sangat pragmatis untuk mengkonfigurasi masalah infrastruktur tertentu sebagai lajang atau variabel global. Contoh favorit saya tentang ini adalah kerangka Ketergantungan Injeksi yang menggunakan lajang untuk bertindak sebagai titik koneksi ke kerangka kerja.

Dalam hal ini Anda mengambil ketergantungan pada infrastruktur untuk menyederhanakan menggunakan perpustakaan dan menghindari kompleksitas yang tidak dibutuhkan.


0

Saya menggunakannya untuk objek mengenkapsulasi parameter baris perintah ketika berhadapan dengan modul pluggable. Program utama tidak tahu apa parameter baris perintah untuk modul yang dimuat (dan bahkan tidak selalu tahu modul apa yang dimuat). misalnya, beban utama A, yang tidak memerlukan parameter apa pun (jadi mengapa harus mengambil pointer / referensi tambahan / apa pun, saya tidak yakin - terlihat seperti polusi), lalu memuat modul X, Y, dan Z. Dua dari ini, katakanlah X dan Z, perlu (atau menerima) parameter, sehingga mereka memanggil kembali ke singleton baris perintah untuk memberi tahu itu parameter apa yang harus diterima, dan pada saat runtime mereka memanggil kembali untuk mencari tahu apakah pengguna benar-benar telah menentukan apa saja dari mereka.

Dalam banyak hal, satu singleton untuk menangani parameter CGI akan bekerja sama jika Anda hanya menggunakan satu proses per kueri (metode mod_ * lainnya tidak melakukan ini, jadi akan buruk di sana - sehingga argumen yang mengatakan Anda tidak boleh t gunakan lajang di dunia mod_cgi jika Anda port ke mod_perl atau dunia apa pun).


-1

Contoh dengan kode, mungkin.

Di sini, ConcreteRegistry adalah singleton dalam permainan poker yang memungkinkan perilaku hingga pohon paket mengakses beberapa, antarmuka inti permainan (yaitu, fasad untuk model, tampilan, pengontrol, lingkungan, dll.):

http://www.edmundkirwan.com/servlet/fractal/cs1/frac-cs40.html

Ed.


1
Tautan sekarang rusak, tetapi jika Anda mendaftarkan informasi tampilan dalam satu singleton, yang akan diakses di seluruh aplikasi, Anda kehilangan titik MVC. Pandangan diperbarui oleh (dan berkomunikasi dengan) pengontrol, yang menggunakan model. Seperti yang terdengar di sini, itu mungkin penyalahgunaan Singleton dan refactoring sedang dilakukan.
drharris

-9

1 - Sebuah komentar pada jawaban pertama:

Saya tidak setuju dengan kelas Logger statis. ini bisa praktis untuk implementasi, tetapi tidak bisa diganti untuk pengujian unit. Kelas statis tidak dapat diganti dengan tes ganda. Jika Anda tidak menguji unit, Anda tidak akan melihat masalah di sini.

2 - Saya mencoba untuk tidak membuat singleton dengan tangan. Saya hanya membuat objek sederhana dengan konstruktor yang memungkinkan saya untuk menyuntikkan kolaborator ke objek. Jika saya membutuhkan singleton, saya akan menggunakan framework inyection ketergantungan (Spring.NET, Unity for .NET, Spring for Java), atau yang lainnya.


11
Anda harus mengomentari jawaban secara langsung dengan mengklik tautan di bagian bawah jawaban; jauh lebih mudah untuk membaca seperti itu. Juga, jawaban yang Anda lihat di atas mungkin bukan yang pertama. Jawaban disusun ulang setiap saat.
Ross

mengapa Anda ingin unit test logging?
Enerccio

Ada perbedaan besar antara "kelas Logger statis" dan contoh Logger statis . Pola singleton tidak mengatakan "buat kelas Anda statis", ia mengatakan untuk membuat akses ke instance objek statis. Jadi misalnya, di ILogger logger = Logger.SingleInstance();mana metode ini statis dan mengembalikan contoh ILogger yang disimpan secara statis. Anda menggunakan contoh "kerangka kerja injeksi ketergantungan". Hampir semua wadah DI adalah lajang; konfigurasi mereka didefinisikan secara statis dan pada akhirnya diakses dari / disimpan dalam antarmuka penyedia layanan tunggal.
Jon Davis
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.