Meskipun pertanyaan umum ruang lingkup saya agak C # karena saya sadar bahwa bahasa seperti C ++ memiliki semantik yang berbeda mengenai eksekusi konstruktor, manajemen memori, perilaku tidak terdefinisi, dll.
Seseorang bertanya kepada saya pertanyaan menarik yang bagi saya tidak mudah dijawab.
Mengapa (atau itu sama sekali?) Dianggap sebagai desain yang buruk untuk membiarkan konstruktor kelas memulai loop yang tidak pernah berakhir (yaitu loop game)?
Ada beberapa konsep yang dirusak oleh ini:
- seperti prinsip yang paling mengejutkan, pengguna tidak mengharapkan konstruktor berperilaku seperti ini.
- Tes unit lebih sulit karena Anda tidak dapat membuat kelas ini atau menyuntikkannya karena tidak pernah keluar dari loop.
- Akhir dari loop (akhir permainan) kemudian secara konseptual adalah waktu di mana konstruktor selesai, yang juga aneh.
- Secara teknis kelas seperti itu tidak memiliki anggota publik kecuali konstruktor, yang membuatnya lebih sulit untuk dipahami (terutama untuk bahasa di mana tidak ada implementasi tersedia)
Dan kemudian ada masalah teknis:
- Konstruktor sebenarnya tidak pernah selesai, jadi apa yang terjadi dengan GC di sini? Apakah objek ini sudah ada dalam Gen 0?
- Berasal dari kelas semacam itu tidak mungkin atau paling tidak sangat rumit karena fakta bahwa konstruktor dasar tidak pernah kembali
Apakah ada sesuatu yang lebih buruk atau licik dengan pendekatan semacam itu?
c#
architecture
Samuel
sumber
sumber
var g = new Game {...}; g.MainLoop();
while(true)
loop dalam setter properti:new Game().RunNow = true
?Jawaban:
Apa tujuan seorang konstruktor? Ini mengembalikan objek yang baru dibangun. Apa yang dilakukan infinite loop? Tidak pernah kembali. Bagaimana konstruktor mengembalikan objek yang baru dibangun jika tidak kembali sama sekali? Tidak bisa.
Ergo, sebuah infinite loop memutus kontrak fundamental konstruktor: untuk membangun sesuatu.
sumber
Ya tentu saja. Itu tidak perlu, tidak terduga, tidak berguna, tidak penting. Ini melanggar konsep desain kelas modern (kohesi, kopling). Itu melanggar kontrak metode (konstruktor memiliki pekerjaan yang ditentukan dan bukan hanya beberapa metode acak). Hal ini tentu saja tidak dipelihara dengan baik, programmer masa depan akan menghabiskan banyak waktu untuk mencoba memahami apa yang sedang terjadi, dan mencoba menebak alasan mengapa hal itu dilakukan dengan cara itu.
Tidak ada yang merupakan "bug" dalam arti kode Anda tidak berfungsi. Tetapi kemungkinan akan menimbulkan biaya sekunder yang besar (relatif terhadap biaya penulisan kode pada awalnya) dalam jangka panjang dengan membuat kode lebih sulit untuk dipertahankan (yaitu, sulit untuk menambahkan tes, sulit untuk digunakan kembali, sulit untuk debug, sulit untuk memperpanjang dll .).
Banyak / kebanyakan peningkatan modern dalam metode pengembangan perangkat lunak dilakukan secara khusus untuk membuat proses penulisan / pengujian / debugging / pemeliharaan perangkat lunak menjadi lebih mudah. Semua ini dielakkan dengan hal-hal seperti ini, di mana kode ditempatkan secara acak karena "berfungsi".
Sayangnya, Anda secara teratur akan bertemu programmer yang sama sekali tidak mengetahui semua ini. Berhasil, itu saja.
Untuk menyelesaikan dengan analogi (bahasa pemrograman lain, di sini masalahnya adalah menghitung
2+2
):Apa yang salah dengan pendekatan pertama? Ia mengembalikan 4 setelah waktu yang cukup singkat; itu benar. Keberatan (bersama dengan semua alasan yang biasa diberikan pengembang untuk membantahnya) adalah:
bash
(tetapi tidak akan pernah berjalan di Windows, Mac atau Android)sumber
fundamental contract of a constructor: to construct something
tepat.Anda memberikan cukup alasan dalam pertanyaan Anda untuk mengesampingkan pendekatan ini tetapi pertanyaan sebenarnya di sini adalah "Apakah ada sesuatu yang lebih jelas buruk atau licik dengan pendekatan seperti itu?"
Namun yang pertama saya di sini adalah bahwa ini tidak ada gunanya. Jika konstruktor Anda tidak pernah selesai, tidak ada bagian lain dari program yang bisa mendapatkan referensi ke objek yang dibangun jadi apa logika di balik meletakkannya di konstruktor alih-alih metode biasa. Kemudian terpikir oleh saya bahwa satu-satunya perbedaan adalah bahwa di loop Anda, Anda dapat memungkinkan referensi untuk sebagian dibangun
this
melarikan diri dari loop. Meskipun ini tidak sepenuhnya terbatas pada situasi ini, dijamin bahwa jika Anda mengizinkanthis
untuk melarikan diri, referensi tersebut akan selalu menunjuk ke objek yang tidak sepenuhnya dibangun.Saya tidak tahu apakah semantik di sekitar situasi seperti ini didefinisikan dengan baik dalam C # tapi saya berpendapat itu tidak masalah karena itu bukan sesuatu yang kebanyakan pengembang ingin coba selidiki.
sumber
this
di konstruktor - tetapi hanya jika Anda berada di konstruktor dari kelas yang paling diturunkan. Jika Anda memiliki dua kelas,A
danB
denganB : A
, jika konstruktorA
akan bocorthis
, anggotaB
masih akan diinisialisasi (dan metode virtual panggilan atau gips untukB
dapat mendatangkan malapetaka berdasarkan itu).+1 Paling tidak heran, tetapi ini adalah konsep yang sulit untuk diartikulasikan ke pengembang baru. Pada tingkat pragmatis, sulit untuk men-debug pengecualian yang muncul di dalam konstruktor, jika objek gagal menginisialisasi itu tidak akan ada bagi Anda untuk memeriksa keadaan atau log dari luar konstruktor itu.
Jika Anda merasa perlu melakukan semacam pola kode ini, silakan gunakan metode statis di kelas sebagai gantinya.
Konstruktor ada untuk memberikan logika inisialisasi ketika sebuah objek diturunkan dari definisi kelas. Anda membangun instance objek ini karena itu sebagai wadah yang merangkum serangkaian properti dan fungsionalitas yang ingin Anda panggil dari sisa logika aplikasi Anda.
Jika Anda tidak berniat menggunakan objek yang Anda "buat", lalu apa gunanya instantiasi objek itu? Memiliki loop sementara (benar) di konstruktor secara efektif berarti Anda tidak pernah bermaksud untuk menyelesaikannya ...
C # adalah bahasa berorientasi objek yang sangat kaya dengan banyak konstruksi dan paradigma yang berbeda untuk Anda jelajahi, ketahui alat Anda dan kapan menggunakannya, intinya:
sumber
Tidak ada yang buruk tentang hal itu. Namun, yang penting adalah pertanyaan mengapa Anda memilih untuk menggunakan konstruk ini. Apa yang Anda dapatkan dengan melakukan ini?
Pertama, saya tidak akan berbicara tentang penggunaan
while(true)
konstruktor. Sama sekali tidak ada yang salah dengan menggunakan sintaks dalam konstruktor. Namun, konsep "memulai permainan di konstruktor" adalah konsep yang dapat menyebabkan beberapa masalah.Sangat umum dalam situasi ini untuk memiliki konstruktor cukup membangun objek, dan memiliki fungsi yang disebut infinite loop. Ini memberi pengguna kode Anda lebih fleksibel. Sebagai contoh dari berbagai masalah yang dapat muncul adalah Anda membatasi penggunaan objek Anda. Jika seseorang ingin memiliki objek anggota yang merupakan "objek permainan" di kelas mereka, mereka harus siap untuk menjalankan seluruh loop permainan di konstruktor mereka karena mereka harus membangun objek. Ini mungkin menyebabkan penyiksaan kode untuk mengatasi hal ini. Dalam kasus umum, Anda tidak dapat mengalokasikan sebelumnya objek game Anda, dan kemudian menjalankannya nanti. Untuk beberapa program itu bukan masalah, tetapi ada kelas program di mana Anda ingin dapat mengalokasikan semuanya di muka dan kemudian memanggil lingkaran permainan.
Salah satu aturan praktis dalam pemrograman adalah menggunakan alat paling sederhana untuk pekerjaan itu. Ini bukan aturan yang keras dan cepat, tapi itu sangat berguna. Jika pemanggilan fungsi sudah cukup, mengapa repot membangun objek kelas? Jika saya melihat alat rumit yang lebih kuat digunakan, saya menganggap pengembang punya alasan untuk itu, dan saya akan mulai mengeksplorasi jenis trik aneh yang mungkin Anda lakukan.
Ada kasus di mana ini mungkin masuk akal. Mungkin ada kasus di mana itu benar-benar intuitif bagi Anda untuk memiliki loop permainan di konstruktor. Dalam kasus itu, Anda menjalankannya! Misalnya, loop game Anda dapat disematkan dalam program yang jauh lebih besar yang membangun kelas untuk mewujudkan beberapa data. Jika Anda harus menjalankan loop game untuk menghasilkan data itu, mungkin sangat masuk akal untuk menjalankan loop game dalam konstruktor. Perlakukan saja ini sebagai kasus khusus: valid untuk melakukan ini karena program menyeluruh membuatnya intuitif bagi pengembang berikutnya untuk memahami apa yang Anda lakukan dan mengapa.
sumber
GameTestResults testResults = runGameTests(tests, configuration);
bukanGameTestResults testResults = new GameTestResults(tests, configuration);
.Kesan saya adalah bahwa beberapa konsep tercampur dan menghasilkan pertanyaan yang tidak terlalu baik.
Konstruktor suatu kelas dapat memulai utas baru yang akan "tidak pernah" berakhir (meskipun saya lebih suka menggunakan
while(_KeepRunning)
lebihwhile(true)
karena saya dapat mengatur anggota boolean ke false di suatu tempat).Anda dapat mengekstrak metode membuat dan memulai utas ke dalam fungsinya sendiri, untuk memisahkan konstruksi objek dan awal pekerjaannya - saya lebih suka cara ini karena saya mendapatkan kontrol yang lebih baik atas akses ke sumber daya.
Anda juga dapat melihat "Pola Objek Aktif". Pada akhirnya, saya kira pertanyaannya adalah kapan memulai thread dari "Objek Aktif", dan preferensi saya adalah de-coupling dari konstruksi dan mulai untuk kontrol yang lebih baik.
sumber
Objek Anda mengingatkan saya untuk memulai Tugas . Tugas objek memiliki konstruktor, tetapi ada juga sekelompok metode pabrik statis seperti:
Task.Run
,Task.Start
danTask.Factory.StartNew
.Ini sangat mirip dengan apa yang Anda coba lakukan, dan kemungkinan ini adalah 'konvensi'. Masalah utama yang tampaknya dimiliki adalah penggunaan konstruktor ini mengejutkan mereka. @ JörgWMittag mengatakan itu melanggar kontrak mendasar , yang saya kira berarti Jorg sangat terkejut. Saya setuju dan tidak ada lagi yang bisa ditambahkan.
Namun, saya ingin menyarankan bahwa OP mencoba metode pabrik statis. Kejutannya lenyap dan tidak ada kontrak fundamental yang dipertaruhkan di sini. Orang-orang terbiasa metode pabrik statis melakukan hal-hal khusus, dan mereka dapat dinamai sesuai.
Anda dapat memberikan konstruktor yang memungkinkan kontrol berbutir halus (seperti @Brandin menyarankan, sesuatu seperti
var g = new Game {...}; g.MainLoop();
, yang mengakomodasi pengguna yang tidak ingin segera memulai permainan, dan mungkin ingin meneruskannya terlebih dahulu. Dan Anda dapat menulis sesuatu sepertivar runningGame = Game.StartNew();
memulai) segera mudah.sumber
Itu tidak buruk sama sekali.
Mengapa Anda ingin melakukan ini? Serius. Kelas itu memiliki fungsi tunggal: konstruktor. Jika semua yang Anda inginkan adalah fungsi tunggal, itu sebabnya kami memiliki fungsi, bukan konstruktor.
Anda menyebutkan beberapa alasan yang jelas menentangnya sendiri, tetapi Anda mengabaikan alasan yang paling besar:
Ada pilihan yang lebih baik dan lebih sederhana untuk mencapai hal yang sama.
Jika ada 2 pilihan dan satu lebih baik, tidak masalah seberapa banyak lebih baik, Anda memilih yang lebih baik. Kebetulan, itu sebabnya kami
tidakhampir tidak pernah menggunakan GoTo.sumber
Tujuan konstruktor adalah untuk, membuat objek Anda. Sebelum konstruktor selesai, pada prinsipnya tidak aman untuk menggunakan metode apa pun dari objek itu. Tentu saja, kita dapat memanggil metode objek dalam konstruktor, tetapi kemudian setiap kali kita melakukannya, kita harus memastikan bahwa hal itu berlaku untuk objek dalam keadaan setengah baken saat ini.
Dengan secara tidak langsung memasukkan semua logika ke dalam konstruktor, Anda menambahkan lebih banyak beban seperti ini ke diri Anda sendiri. Ini menunjukkan bahwa Anda tidak merancang perbedaan antara inisialisasi dan penggunaan yang cukup baik.
sumber