Saya menggunakan mesin game cocos2d-x untuk membuat game. Mesinnya sudah menggunakan banyak lajang. Jika seseorang menggunakannya, maka mereka harus terbiasa dengan beberapa di antaranya:
Director
SimpleAudioEngine
SpriteFrameCache
TextureCache
EventDispatcher (was)
ArmatureDataManager
FileUtils
UserDefault
dan banyak lagi dengan keseluruhan sekitar 16 kelas. Anda dapat menemukan daftar serupa di halaman ini: Benda-benda singleton di Cocos2d-html5 v3.0 Tetapi ketika saya ingin menulis permainan saya, saya membutuhkan lebih banyak lajang:
PlayerData (score, lives, ...)
PlayerProgress (passed levels, stars)
LevelData (parameters per levels and level packs)
SocialConnection (Facebook and Twitter login, share, friend list, ...)
GameData (you may obtain some data from server to configure the game)
IAP (for in purchases)
Ads (for showing ads)
Analytics (for collecting some analytics)
EntityComponentSystemManager (mananges entity creation and manipulation)
Box2dManager (manages the physics world)
.....
Mengapa saya pikir mereka harus menjadi lajang? Karena saya akan membutuhkannya di tempat yang sangat berbeda dalam permainan saya, dan akses bersama akan sangat berguna. Dengan kata lain, saya tidak harus membuatnya di suatu tempat dan meneruskan petunjuk ke semua arsitektur saya karena akan sangat sulit. Juga ini adalah hal-hal yang saya hanya perlu satu. Bagaimanapun saya membutuhkan beberapa, saya dapat menggunakan pola Multiton juga. Tetapi yang terburuk adalah bahwa Singleton adalah pola yang paling banyak dikritik karena:
- bad testability
- no inheritance available
- no lifetime control
- no explicit dependency chain
- global access (the same as global variables, actually)
- ....
Anda dapat menemukan beberapa pemikiran di sini: https://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons dan https://stackoverflow.com/questions/4074154/when-should-the-singleton -pola-tidak-digunakan-selain-yang-jelas
Jadi, saya pikir, saya melakukan sesuatu yang salah. Saya pikir kode saya berbau . :) Saya ingat bagaimana pengembang game yang lebih berpengalaman memecahkan masalah arsitektur ini? Saya ingin memeriksa, mungkin masih normal dalam pengembangan game untuk memiliki lebih dari 30 lajang , dianggap yang sudah ada di mesin game.
Saya telah berpikir untuk menggunakan Singleton-Facade yang akan memiliki turunan dari semua kelas yang saya butuhkan, tetapi masing-masing dari mereka belum akan menjadi lajang. Ini akan menghilangkan banyak masalah, dan saya hanya akan memiliki satu singleton yang akan menjadi Fasad itu sendiri. Tetapi dalam hal ini saya akan memiliki masalah desain lain. Fasad akan menjadi TUJUAN ALLAH. Saya pikir ini juga baunya . Jadi saya tidak dapat menemukan solusi desain yang bagus untuk situasi ini. Tolong saran.
sumber
Jawaban:
Saya menginisialisasi layanan saya di kelas aplikasi utama saya dan kemudian meneruskannya sebagai petunjuk untuk apa pun yang perlu menggunakannya baik melalui konstruktor atau fungsi. Ini berguna karena dua alasan.
Pertama, urutan inisialisasi dan pembersihan sederhana dan jelas. Tidak ada cara untuk secara tidak sengaja menginisialisasi satu layanan di tempat lain seperti Anda dapat menggunakan singleton.
Dua, jelas siapa yang bergantung pada apa. Saya dapat dengan mudah melihat kelas apa yang membutuhkan layanan apa saja dari deklarasi kelas.
Anda mungkin berpikir menjengkelkan untuk melewatkan semua objek ini, tetapi jika sistem dirancang dengan baik, itu benar-benar tidak buruk sama sekali dan membuat segalanya lebih jelas (bagi saya toh).
sumber
Saya tidak akan membahas tentang kejahatan di balik lajang karena Internet dapat melakukan itu lebih baik daripada saya.
Dalam permainan saya, saya menggunakan pola Service Locator untuk menghindari memiliki banyak Lajang / Manajer.
Konsepnya cukup sederhana. Anda hanya memiliki satu Singleton yang bertindak seperti satu-satunya antarmuka untuk mencapai apa yang Anda gunakan sebagai Singleton. Alih-alih memiliki beberapa Singleton Anda sekarang hanya memiliki satu.
Panggilan seperti:
Sepertinya ini, menggunakan pola Layanan Locator:
Seperti yang Anda lihat, setiap Singleton terkandung di dalam Service Locator.
Untuk memiliki penjelasan yang lebih rinci saya sarankan Anda membaca halaman ini tentang cara menggunakan Pola Locator .
[ EDIT ]: seperti yang ditunjukkan dalam komentar, situs gameprogrammingpatterns.com juga berisi bagian yang sangat menarik tentang Singleton .
Saya harap ini membantu.
sumber
Membaca semua jawaban, komentar, dan artikel yang ditunjukkan, terutama dua artikel brilian ini,
akhirnya, saya sampai pada kesimpulan berikut, yang merupakan semacam jawaban untuk pertanyaan saya sendiri. Pendekatan terbaik adalah tidak menjadi malas dan lulus ketergantungan secara langsung. Ini eksplisit, dapat diuji, tidak ada akses global, dapat menunjukkan desain yang salah jika Anda harus melewati delegasi yang sangat dalam dan di banyak tempat dan sebagainya. Tetapi dalam pemrograman satu ukuran tidak cocok untuk semua. Dengan demikian, masih ada kasus yang tidak mudah untuk dilewatkan secara langsung. Sebagai contoh jika kita membuat kerangka permainan (template kode yang akan digunakan kembali sebagai kode dasar untuk mengembangkan berbagai jenis game), dan menyertakan banyak layanan di sana. Di sini kita tidak tahu seberapa dalam setiap layanan dapat dilewati, dan berapa banyak tempat yang diperlukan. Itu tergantung pada game tertentu. Jadi apa yang kita lakukan dalam kasus seperti ini? Kami menggunakan pola desain Service Locator alih-alih Singleton,
Kita juga harus mempertimbangkan bahwa pola ini digunakan dalam Unity, LibGDX, XNA. Ini bukan keuntungan, tetapi ini adalah semacam bukti dari kegunaan pola. Pertimbangkan bahwa mesin ini dikembangkan oleh banyak pengembang cerdas sejak lama dan selama fase evolusi mesin, mereka masih belum menemukan solusi yang lebih baik.
Sebagai kesimpulan, saya pikir Service Locator dapat sangat berguna untuk skenario yang dijelaskan dalam pertanyaan saya, tetapi masih harus dihindari jika memungkinkan. Sebagai aturan praktis - gunakan jika Anda harus.
EDIT: Setelah satu tahun bekerja dan menggunakan DI langsung dan memiliki masalah dengan konstruktor besar dan banyak delegasi, saya telah melakukan lebih banyak penelitian dan telah menemukan artikel bagus yang berbicara tentang pro dan kontra tentang metode DI dan Service Locator. Mengutip artikel ini ( http://www.martinfowler.com/articles/injection.html ) oleh Martin Fowler yang menjelaskan kapan sebenarnya pencari Layanan buruk:
Tapi bagaimanapun, jika Anda ingin kami DI pertama kali, Anda harus membaca ini http://misko.hevery.com/2008/10/21/dependency-injection-myth-reference-passing/ artikel (ditunjukkan oleh @megadan) , dengan memberikan perhatian yang sangat hati-hati pada Hukum Demeter (LoD) atau prinsip pengetahuan paling rendah .
sumber
Tidak jarang bagian basis kode dianggap objek landasan atau kelas dasar, tetapi itu tidak membenarkan siklus hidupnya untuk didikte sebagai Singleton.
Programmer sering mengandalkan pola Singleton sebagai sarana kenyamanan dan kemalasan murni daripada mengambil pendekatan alternatif dan menjadi sedikit lebih verbose dan memaksakan hubungan objek antara satu sama lain dalam manor eksplisit.
Jadi tanyakan pada diri sendiri mana yang lebih mudah dirawat.
Jika Anda seperti saya, saya lebih memilih untuk dapat membuka file header dan secara singkat membaca argumen konstruktor dan metode publik untuk melihat dependensi apa yang dibutuhkan suatu objek daripada harus memeriksa ratusan baris kode dalam file implementasi.
Jika Anda telah menjadikan sebuah objek Singleton dan kemudian menyadari bahwa Anda harus mampu mendukung banyak contoh objek tersebut, bayangkan perubahan besar diperlukan jika kelas ini adalah sesuatu yang Anda gunakan cukup banyak di seluruh basis kode Anda. Alternatif yang lebih baik adalah dengan membuat dependensi menjadi eksplisit, bahkan jika objek hanya dialokasikan satu kali dan jika perlu muncul untuk mendukung banyak instance, Anda dapat melakukannya dengan aman tanpa kekhawatiran tersebut.
Seperti yang telah ditunjukkan orang lain, penggunaan pola Singleton juga mengaburkan kopling yang sering menandakan desain yang buruk dan kohesi yang tinggi antara modul. Kohesi semacam itu biasanya tidak diinginkan dan mengarah ke kode rapuh yang sulit dipertahankan.
sumber
Singleton adalah pola yang terkenal, tetapi ada baiknya mengetahui tujuan yang dilayaninya, dan pro dan kontra.
Sangat masuk akal jika tidak ada hubungan sama sekali.
Jika Anda dapat menangani komponen Anda dengan objek yang sama sekali berbeda (tidak ada ketergantungan kuat) dan berharap memiliki perilaku yang sama, singleton mungkin merupakan pilihan yang baik.
Di sisi lain, jika Anda memerlukan fungsionalitas kecil , hanya digunakan pada waktu-waktu tertentu, Anda sebaiknya memilih untuk melewatkan referensi .
Seperti yang Anda sebutkan, lajang tidak memiliki kendali seumur hidup. Tetapi keuntungannya adalah mereka memiliki inisialisasi yang malas.
Jika Anda tidak memanggil singleton itu dalam masa aplikasi Anda, itu tidak akan dipakai. Tapi saya ragu Anda membangun lajang dan tidak menggunakannya.
Anda harus melihat singleton seperti layanan, bukan komponen .
Singletons bekerja, mereka tidak pernah merusak aplikasi apa pun. Tetapi kekurangan mereka (empat yang Anda berikan) adalah alasan Anda tidak akan membuatnya.
sumber
Ketidaktahuan bukanlah alasan mengapa lajang perlu dihindari.
Ada banyak alasan bagus mengapa Singleton diperlukan atau tidak dapat dihindari. Kerangka permainan sering menggunakan lajang karena merupakan konsekuensi yang tidak terhindarkan dari hanya memiliki satu perangkat keras stateful. Tidak masuk akal untuk pernah ingin mengendalikan perangkat keras ini dengan beberapa contoh penangan masing-masing. Permukaan grafik adalah perangkat keras stateful eksternal dan secara tidak sengaja menginisialisasi salinan kedua dari subsistem grafis tidak kekurangan bencana, karena sekarang dua subsistem grafis akan bertengkar satu sama lain tentang siapa yang akan menggambar dan kapan, menimpa satu sama lain secara tak terkendali. Demikian juga dengan sistem acara antrian, mereka akan berebut siapa yang mendapatkan acara mouse dengan cara nondeterministic. Ketika berhadapan dengan perangkat keras eksternal stateful di mana hanya ada satu dari mereka, singleton tidak dapat dihindari untuk mencegah konflik.
Tempat lain di mana Singleton masuk akal adalah dengan manajer Cache. Tembolok adalah kasus khusus. Mereka seharusnya tidak benar-benar dianggap sebagai Singleton, bahkan ketika mereka menggunakan semua teknik yang sama seperti Singletons untuk tetap hidup dan hidup selamanya. Layanan caching adalah layanan transparan, mereka tidak seharusnya mengubah perilaku program, jadi jika Anda mengganti layanan cache dengan cache nol, program harus tetap berfungsi, kecuali bahwa itu hanya berjalan lebih lambat. Alasan utama mengapa manajer cache pengecualian untuk singleton adalah karena tidak masuk akal untuk mematikan layanan cache sebelum mematikan aplikasi itu sendiri karena itu juga akan membuang objek yang di-cache, yang mengalahkan titik memilikinya sebagai singleton.
Itulah alasan bagus mengapa layanan ini masih lajang. Namun, tidak ada alasan yang baik untuk memiliki lajang berlaku untuk kelas yang telah Anda daftarkan.
Itu bukan alasan untuk lajang. Itulah alasan global. Ini juga merupakan tanda desain yang buruk jika Anda harus melewati banyak hal di berbagai sistem. Harus melewati hal-hal di sekitar menunjukkan kopling tinggi yang dapat dicegah dengan desain OO yang baik.
Hanya dari melihat daftar kelas di postingan Anda yang menurut Anda perlu lajang, saya dapat mengatakan bahwa setengah dari mereka benar-benar tidak boleh lajang dan setengah lainnya sepertinya mereka bahkan tidak seharusnya ada di sana. Bahwa Anda perlu melewati objek tampaknya disebabkan oleh kurangnya enkapsulasi yang tepat daripada kasus penggunaan yang baik untuk lajang.
Mari kita lihat kelas Anda sedikit demi sedikit:
Hanya dari nama-nama kelas, saya akan mengatakan bahwa kelas-kelas ini berbau seperti antipattern Model Domain Anemic. Objek anemia biasanya menghasilkan banyak data yang perlu dilewatkan yang meningkatkan kopling dan membuat sisa kode rumit. Juga, kelas anemia menyembunyikan fakta bahwa Anda mungkin masih berpikir secara prosedural daripada menggunakan orientasi objek untuk merangkum detail.
Mengapa kelas-kelas ini harus lajang? Tampaknya bagi saya ini adalah kelas yang berumur pendek, yang harus dimunculkan ketika dibutuhkan dan didekonstruksi ketika pengguna menyelesaikan pembelian atau ketika iklan tidak perlu lagi ditampilkan.
Dengan kata lain, sebuah konstruktor dan lapisan layanan? Mengapa kelas ini tanpa batasan atau tujuan yang jelas bahkan ada di tempat pertama?
Mengapa perkembangan pemain terpisah dari kelas Player? Kelas Player harus tahu cara melacak kemajuannya sendiri, jika Anda ingin menerapkan pelacakan kemajuan di kelas yang berbeda dari kelas Player untuk pemisahan tanggung jawab, maka PlayerProgress harus berada di belakang Player.
Saya benar-benar tidak dapat berkomentar lebih jauh tentang ini tanpa mengetahui apa yang sebenarnya dilakukan kelas ini, tetapi satu hal yang jelas adalah bahwa kelas ini tidak disebutkan namanya.
Ini sepertinya satu-satunya kelas di mana Singleton mungkin masuk akal, karena objek koneksi sosial sebenarnya bukan objek Anda. Koneksi sosial hanyalah bayangan dari objek eksternal yang hidup dalam layanan jarak jauh. Dengan kata lain, ini adalah semacam cache. Pastikan Anda dengan jelas memisahkan bagian caching dengan bagian layanan dari layanan eksternal, karena bagian terakhir seharusnya tidak perlu menjadi singleton.
Salah satu cara Anda dapat menghindari lewat instance dari kelas-kelas ini di sekitar adalah dengan menggunakan message passing. Daripada melakukan panggilan langsung ke instance dari kelas-kelas ini, alih-alih Anda mengirim pesan yang ditujukan ke layanan Analytic dan SocialConnection secara tidak sinkron, dan layanan ini berlangganan untuk menerima pesan-pesan ini dan bertindak berdasarkan pesan tersebut. Karena antrian acara sudah menjadi singleton, dengan menginjak-injak panggilan, Anda menghindari keharusan menyampaikan contoh aktual saat berkomunikasi dengan singleton.
sumber
Lajang bagi saya SELALU buruk.
Dalam arsitektur saya, saya membuat koleksi GameServices yang dikelola kelas permainan. Saya dapat mencari tambah dan hapus dari koleksi ini sesuai kebutuhan.
Dengan mengatakan sesuatu berada dalam lingkup global, Anda pada dasarnya mengatakan "hing ini adalah tuhan dan tidak memiliki tuan" dalam contoh yang Anda berikan di atas, saya akan mengatakan sebagian besar dari mereka adalah kelas pembantu atau GameServices yang dalam hal ini permainan akan bertanggung jawab untuk mereka.
Tapi itu hanya desain saya di mesin saya, situasi Anda mungkin berbeda.
Menempatkannya lebih langsung ... Satu-satunya singleton nyata adalah permainan karena memiliki setiap bagian dari kode.
Jawaban ini didasarkan pada sifat subyektif dari pertanyaan dan didasarkan murni pada pendapat saya, mungkin tidak benar untuk semua pengembang di luar sana.
Edit: Seperti yang diminta ...
Ok ini dari kepala saya karena saya tidak memiliki kode saya di depan saya saat ini tetapi pada dasarnya di akar setiap permainan adalah kelas Game yang benar-benar tunggal ... itu pasti!
Jadi saya menambahkan yang berikut ...
Sekarang saya juga memiliki kelas sistem (statis) yang berisi semua lajang saya dari seluruh program (berisi sangat sedikit, permainan dan opsi konfigurasi dan hanya itu saja) ...
Dan penggunaan ...
Dari mana saja saya dapat melakukan sesuatu seperti
Pendekatan ini berarti bahwa permainan saya "Milik" hal-hal yang biasanya akan dianggap sebagai "singleton" dan saya dapat memperluas kelas dasar GameService seperti yang saya inginkan. Saya memiliki antarmuka seperti IUpdateable yang diperiksa permainan saya dan memanggil layanan apa pun yang mengimplementasikannya sesuai kebutuhan untuk situasi tertentu.
Saya juga memiliki layanan yang mewarisi / memperluas layanan lain untuk skenario pemandangan yang lebih spesifik.
Sebagai contoh ...
Saya memiliki layanan Fisika yang menangani simulasi fisika saya tetapi tergantung pada adegan simulasi fisika mungkin memerlukan beberapa perilaku yang sedikit berbeda.
sumber
var tService = Sys.Game.getService<T>(); tService.Something();
alih-alih melewatkangetService
bagian dan mengaksesnya secara langsung?Unsur penting dari penghapusan lajang yang sering gagal dipertimbangkan oleh lawan lajang adalah menambahkan logika ekstra untuk menangani keadaan yang jauh lebih rumit yang diringkas ketika benda-benda lajang memberi jalan pada nilai-nilai yang instan. Memang, bahkan mendefinisikan keadaan objek setelah mengganti singleton dengan variabel instan bisa sulit, tetapi programmer yang mengganti singleton dengan variabel instan harus siap untuk menghadapinya.
Bandingkan, misalnya, Java
HashMap
dengan .NET'sDictionary
. Keduanya sangat mirip, tetapi mereka memiliki perbedaan utama: JavaHashMap
adalah hard-coded untuk digunakanequals
danhashCode
(secara efektif menggunakan pembanding singleton) sementara .NETDictionary
dapat menerima contohIEqualityComparer<T>
sebagai parameter konstruktor. Biaya run-time untuk mempertahankannyaIEqualityComparer
minimal, tetapi kompleksitas yang ditimbulkannya tidak.Karena setiap
Dictionary
instance mengenkapsulasi aIEqualityComparer
, statusnya bukan hanya pemetaan antara kunci yang sebenarnya dikandungnya dan nilai terkaitnya, tetapi juga pemetaan antara set ekivalensi yang ditentukan oleh kunci dan komparator serta nilai terkaitnya. Jika dua contohd1
dand2
dariDictionary
referensi terus ke yang samaIEqualityComparer
, maka akan mungkin untuk menghasilkan sebuah contoh barud3
sehingga untuk setiapx
,d3.ContainsKey(x)
akan samad1.ContainsKey(x) || d2.ContainsKey(x)
. Namun, jikad1
dand2
dapat memiliki referensi ke berbagai pembanding kesetaraan sewenang-wenang, secara umum tidak akan ada cara untuk menggabungkan mereka untuk memastikan bahwa kondisi tersebut berlaku.Menambahkan variabel instan dalam upaya untuk menghilangkan lajang, tetapi gagal untuk memperhitungkan dengan benar kemungkinan keadaan baru yang dapat diimplikasikan oleh variabel instan tersebut dapat membuat kode yang akhirnya menjadi lebih rapuh daripada seharusnya dengan singleton. Jika setiap instance objek memiliki bidang yang terpisah, tetapi hal-hal akan mengalami kegagalan fungsi yang buruk kecuali mereka semua mengidentifikasi instance objek yang sama, maka semua bidang baru telah dibeli adalah semakin banyak cara kesalahan bisa terjadi.
sumber