Mengapa saya harus menggunakan metode inisialisasi dan pembersihan terpisah daripada menempatkan logika di konstruktor dan destruktor untuk komponen mesin?


9

Saya sedang mengerjakan mesin gim saya sendiri, dan saya sedang merancang manajer saya. Saya telah membaca bahwa untuk manajemen memori, menggunakan Init()dan CleanUp()fungsinya lebih baik daripada menggunakan konstruktor dan destruktor.

Saya telah mencari contoh kode C ++, untuk melihat bagaimana fungsi-fungsi itu bekerja dan bagaimana saya bisa mengimplementasikannya ke dalam mesin saya. Bagaimana Init()dan CleanUp()bekerja, dan bagaimana saya bisa menerapkannya ke dalam mesin saya?



Untuk C ++, lihat stackoverflow.com/questions/3786853/... Alasan utama untuk menggunakan Init () adalah 1) Mencegah perkecualian dan crash pada konstruktor dengan fungsi pembantu 4) sebagai metode pribadi untuk menghindari duplikasi kode
brita_

Jawaban:


12

Sebenarnya cukup sederhana:

Alih-alih memiliki Konstruktor yang melakukan pengaturan Anda,

// c-family pseudo-code
public class Thing {
    public Thing (a, b, c, d) { this.x = a; this.y = b; /* ... */ }
}

... minta konstruktor Anda melakukan sedikit atau tidak sama sekali, dan tulis metode yang disebut .initatau .initialize, yang akan melakukan apa yang biasanya dilakukan konstruktor Anda.

public class Thing {
    public Thing () {}
    public void initialize (a, b, c, d) {
        this.x = a; /*...*/
    }
}

Jadi sekarang, bukannya hanya seperti:

Thing thing = new Thing(1, 2, 3, 4);

Anda bisa pergi:

Thing thing = new Thing();

thing.doSomething();
thing.bind_events(evt_1, evt_2);
thing.initialize(1, 2, 3, 4);

Keuntungannya adalah sekarang Anda dapat menggunakan dependensi-injeksi / inversi-kontrol lebih mudah di sistem Anda.

Alih-alih mengatakan

public class Soldier {
    private Weapon weapon;

    public Soldier (name, x, y) {
        this.weapon = new Weapon();
    }
}

Anda dapat membangun tentara, memberinya metode melengkapi, di mana Anda menyerahkan dia senjata, dan KEMUDIAN memanggil semua sisa fungsi konstruktor.

Jadi sekarang, alih-alih musuh subkelas di mana satu tentara memiliki pistol dan yang lain memiliki senapan dan yang lain memiliki senapan, dan itulah satu-satunya perbedaan, Anda bisa mengatakan:

Soldier soldier1 = new Soldier(),
        soldier2 = new Soldier(),
        soldier3 = new Soldier();

soldier1.equip(new Pistol());
soldier2.equip(new Rifle());
soldier3.equip(new Shotgun());

soldier1.initialize("Bob",  32,  48);
soldier2.initialize("Doug", 57, 200);
soldier3.initialize("Mike", 92,  30);

Kesepakatan yang sama dengan kehancuran. Jika Anda memiliki kebutuhan khusus (menghapus pendengar acara, menghapus instance dari array / struktur apa pun yang bekerja dengan Anda, dll.), Anda kemudian akan memanggil mereka secara manual, sehingga Anda tahu persis kapan dan di mana dalam program yang sedang terjadi.

EDIT


Seperti yang ditunjukkan oleh Kryotan, di bawah ini, ini menjawab "Bagaimana" posting asli , tetapi tidak benar-benar melakukan pekerjaan yang baik dari "Mengapa".

Seperti yang mungkin Anda lihat dalam jawaban di atas, mungkin tidak ada banyak perbedaan antara:

var myObj = new Object();
myObj.setPrecondition(1);
myObj.setOtherPrecondition(2);
myObj.init();

dan menulis

var myObj = new Object(1,2);

sementara hanya memiliki fungsi konstruktor yang lebih besar.
Ada argumen yang harus dibuat untuk objek yang memiliki 15 atau 20 pra-kondisi, yang akan membuat konstruktor sangat, sangat sulit untuk dikerjakan, dan itu akan membuat segalanya lebih mudah untuk dilihat dan diingat, dengan menarik hal-hal itu ke antarmuka. , sehingga Anda dapat melihat cara instantiation bekerja, satu tingkat lebih tinggi.

Konfigurasi opsional objek adalah ekstensi alami untuk ini; secara opsional mengatur nilai pada antarmuka, sebelum membuat objek berjalan.
JS memiliki beberapa cara pintas yang bagus untuk ide ini, yang sepertinya tidak sesuai dengan bahasa c-type yang lebih kuat.

Yang mengatakan, kemungkinannya adalah, jika Anda berurusan dengan daftar argumen yang lama di konstruktor Anda, bahwa objek Anda terlalu besar dan melakukan terlalu banyak, sebagaimana adanya. Sekali lagi, ini adalah hal preferensi pribadi, dan ada pengecualian jauh dan luas, tetapi jika Anda memasukkan 20 hal ke dalam objek, kemungkinan besar Anda bisa menemukan cara untuk membuat objek itu melakukan lebih sedikit, dengan membuat objek yang lebih kecil .

Alasan yang lebih relevan, dan yang dapat diterapkan secara luas adalah bahwa inisialisasi objek bergantung pada data asinkron, yang saat ini tidak Anda miliki.

Anda tahu bahwa Anda memerlukan objek, jadi Anda tetap akan membuatnya, tetapi untuk membuatnya berfungsi dengan baik, ia membutuhkan data dari server, atau dari file lain yang sekarang perlu dimuat.

Sekali lagi, apakah Anda mengirimkan data yang diperlukan ke init raksasa, atau membangun antarmuka tidak terlalu penting untuk konsep, sebanyak itu penting untuk antarmuka objek Anda, dan desain sistem Anda ...

Tetapi dalam hal membangun objek, Anda mungkin melakukan sesuatu seperti ini:

var obj_w_async_dependencies = new Object();
async_loader.load(obj_w_async_dependencies.async_data, obj_w_async_dependencies);

async_loader mungkin melewati nama file, atau nama sumber daya atau apa pun, memuat sumber daya itu - mungkin memuat file suara, atau data gambar, atau mungkin memuat statistik karakter yang disimpan ...

... dan kemudian akan memasukkan data itu kembali ke obj_w_async_dependencies.init(result);.

Jenis dinamis ini sering ditemukan di aplikasi web.
Tidak harus dalam konstruksi objek, untuk aplikasi tingkat yang lebih tinggi: misalnya, galeri mungkin memuat dan menginisialisasi segera, dan kemudian menampilkan foto saat mereka streaming - itu tidak benar-benar inisialisasi async, tetapi di mana itu terlihat lebih sering akan menjadi di perpustakaan JavaScript.

Satu modul mungkin tergantung pada yang lain, dan inisialisasi modul itu mungkin ditunda sampai pemuatan tanggungan selesai.

Dalam hal contoh khusus game ini, pertimbangkan Gamekelas yang sebenarnya .

Mengapa kita tidak bisa memanggil .startatau .rundi konstruktor?
Sumber daya perlu dimuat - sisanya semuanya telah cukup banyak didefinisikan dan baik untuk dijalankan, tetapi jika kita mencoba menjalankan game tanpa koneksi database, atau tanpa tekstur atau model atau suara atau level, itu tidak akan menjadi permainan yang sangat menarik ...

... jadi apa, lalu perbedaan antara apa yang kita lihat dari tipikal Game, kecuali bahwa kita memberikan metode "maju" nama yang lebih menarik daripada .init(atau sebaliknya, pisahkan inisialisasi lebih jauh terpisah, untuk memisahkan pemuatan, mengatur hal-hal yang telah dimuat, dan menjalankan program ketika semuanya telah diatur).


2
" Anda kemudian akan memanggil mereka secara manual, sehingga Anda tahu persis kapan dan di mana dalam program yang sedang terjadi. " Satu-satunya waktu di C ++ di mana destruktor akan secara implisit dipanggil adalah untuk objek stack (atau global). Tumpukan benda yang dialokasikan membutuhkan penghancuran eksplisit. Jadi selalu jelas ketika objek sedang dialokasikan.
Nicol Bolas

6
Tidak tepat untuk mengatakan bahwa Anda memerlukan metode terpisah ini untuk memungkinkan injeksi berbagai jenis senjata, atau bahwa ini adalah satu-satunya cara untuk menghindari proliferasi subkelas. Anda dapat mengirimkan contoh senjata melalui konstruktor! Jadi -1 dari saya karena ini bukan kasus penggunaan yang menarik.
Kylotan

1
-1 Dari saya juga, untuk alasan yang hampir sama dengan Kylotan. Anda tidak membuat argumen yang sangat meyakinkan, semua ini bisa dilakukan dengan konstruktor.
Paul Manta

Ya, itu bisa diselesaikan dengan konstruktor dan destruktor. Dia meminta kasus penggunaan teknik dan mengapa dan bagaimana, daripada bagaimana mereka bekerja atau mengapa mereka melakukannya. Memiliki sistem berbasis komponen di mana Anda memiliki metode setter / binding, versus parameter konstruktor untuk DI benar-benar semua bermuara pada bagaimana Anda ingin membangun antarmuka Anda. Tetapi jika objek Anda membutuhkan 20 komponen IOC, apakah Anda ingin menempatkan SEMUA dari mereka ke konstruktor Anda? Bisakah kamu? Tentu saja Anda bisa. Seharusnya kamu? Mungkin tidak. Jika Anda memilih untuk tidak melakukannya, maka apakah Anda memerlukan .init, mungkin tidak, tetapi kemungkinan. Ergo, kasus yang valid.
Norguard

1
@Kylotan Saya sebenarnya mengedit judul pertanyaan untuk menanyakan alasannya. OP hanya bertanya "bagaimana". Saya mengajukan pertanyaan untuk memasukkan "mengapa" sebagai "bagaimana" sepele bagi siapa saja yang tahu apa-apa tentang pemrograman ("Pindahkan saja logika yang akan Anda miliki ke dalam ctor ke dalam fungsi yang terpisah dan sebut saja") dan "mengapa" lebih menarik / umum.
Tetrad

17

Apa pun yang Anda baca yang mengatakan Init dan CleanUp lebih baik, seharusnya juga memberi tahu Anda alasannya. Artikel yang tidak membenarkan klaim mereka tidak layak dibaca.

Memiliki fungsi inisialisasi dan shutdown yang terpisah dapat membuatnya lebih mudah untuk mengatur dan menghancurkan sistem karena Anda dapat memilih urutan untuk memanggilnya, sedangkan konstruktor dipanggil secara tepat ketika objek dibuat dan destruktor disebut ketika objek dihancurkan. Ketika Anda memiliki dependensi kompleks antara 2 objek, Anda sering membutuhkan keduanya untuk eksis sebelum mereka mengatur sendiri - tetapi seringkali ini adalah tanda desain yang buruk di tempat lain.

Beberapa bahasa tidak memiliki destruktor yang dapat Anda andalkan, karena penghitungan referensi dan pengumpulan sampah membuat lebih sulit untuk mengetahui kapan objek akan dihancurkan. Dalam bahasa-bahasa ini Anda hampir selalu membutuhkan metode shutdown / pembersihan, dan beberapa suka menambahkan metode init untuk simetri.


Terima kasih, tetapi saya terutama mencari contoh, karena artikel itu tidak memilikinya. Saya minta maaf jika pertanyaan saya tidak jelas tentang itu, tetapi saya telah mengeditnya sekarang.
Friso

3

Saya pikir alasan terbaik adalah: untuk memungkinkan pengumpulan.
jika Anda memiliki Init dan CleanUp, Anda dapat, ketika sebuah objek terbunuh, cukup panggil CleanUp, dan dorong objek tersebut ke tumpukan objek dengan tipe yang sama: a 'pool'.
Kemudian, kapan pun Anda membutuhkan objek baru, Anda dapat memunculkan satu objek dari pool ATAU jika pool kosong -terlalu buruk- Anda harus membuat yang baru. Kemudian Anda memanggil Init pada objek ini.
Strategi yang baik adalah mengisi terlebih dahulu kumpulan sebelum permainan dimulai dengan jumlah objek yang 'bagus', sehingga Anda tidak perlu membuat objek yang dikumpulkan selama permainan.
Jika, di sisi lain, Anda menggunakan 'baru', dan hanya berhenti merujuk objek ketika tidak berguna bagi Anda, Anda membuat sampah yang harus diingat kembali pada suatu waktu. Ingatan ini terutama merupakan hal yang buruk untuk bahasa single-threaded seperti Javascript, di mana pengumpul sampah menghentikan semua kode ketika mengevaluasi perlu mengingat kembali memori objek yang tidak lagi digunakan. Gim ini hang selama beberapa milidetik, dan pengalaman bermainnya manja.
- Anda sudah mengerti -: jika Anda mengumpulkan semua objek Anda, tidak ada ingatan yang terjadi, maka tidak ada lagi perlambatan acak.

Ini juga jauh lebih cepat untuk memanggil init pada objek yang datang dari kolam daripada mengalokasikan memori + init objek baru.
Tetapi peningkatan kecepatan kurang penting, karena sering kali penciptaan objek bukan hambatan kinerja ... Dengan beberapa pengecualian, seperti game panik, mesin partikel, atau mesin fisik menggunakan vektor 2D / 3d intensif untuk perhitungan mereka. Di sini kecepatan dan pembuatan sampah sangat ditingkatkan dengan menggunakan kolam.

Rq: Anda mungkin tidak perlu memiliki metode CleanUp untuk objek yang dikumpulkan Anda jika Init () me-reset semuanya.

Sunting: membalas posting ini memotivasi saya untuk menyelesaikan sedikit artikel yang saya buat tentang penyatuan dalam Javascript .
Anda dapat menemukannya di sini jika Anda tertarik:
http://gamealchemist.wordpress.com/


1
-1: Anda tidak perlu melakukan ini hanya untuk memiliki kumpulan objek. Anda dapat melakukannya dengan hanya memisahkan alokasi dari konstruksi melalui penempatan yang baru dan penghapusan alokasi dari penghapusan dengan panggilan destruktor eksplisit. Jadi ini bukan alasan yang valid untuk memisahkan konstruktor / destruktor dari beberapa metode penginisialisasi.
Nicol Bolas

penempatan baru adalah khusus C ++, dan sedikit esoterik juga.
Kylotan

Memberi +1 dimungkinkan untuk melakukan ini dengan cara lain di c +. Tetapi tidak dalam bahasa lain ... dan ini mungkin satu-satunya alasan mengapa saya akan menggunakan metode Init pada gameobjects.
Kikaimaru

1
@Nicol Bolas: saya pikir Anda bereaksi berlebihan. Fakta bahwa ada cara lain untuk melakukan pooling (Anda menyebutkan yang kompleks, khusus untuk C ++) tidak membatalkan fakta bahwa menggunakan Init yang terpisah adalah cara yang bagus dan sederhana untuk mengimplementasikan pooling dalam banyak bahasa. preferensi saya, di GameDev, untuk jawaban yang lebih umum.
GameAlchemist

@VincentPiel: Bagaimana menggunakan penempatan "kompleks" baru dan seperti itu di C ++? Juga, jika Anda bekerja dalam bahasa GC, kemungkinan besar benda-benda akan mengandung objek berbasis GC. Jadi apakah mereka juga harus memilih masing-masing? Jadi, membuat objek baru akan melibatkan mendapatkan banyak objek baru dari kumpulan.
Nicol Bolas

0

Pertanyaan Anda dibalik ... Secara historis, pertanyaan yang lebih relevan adalah:

Mengapa adalah konstruksi + intialisation digabungkan , yaitu mengapa tidak kita lakukan langkah-langkah ini secara terpisah? Tentunya ini bertentangan dengan SoC ?

Untuk C ++, maksud RAII adalah bahwa akuisisi dan pelepasan sumber daya terkait langsung dengan objek seumur hidup, dengan harapan hal ini akan menjamin pelepasan sumber daya. Melakukannya? Sebagian. Ini dipenuhi 100% dalam konteks variabel berbasis-stack / otomatis, di mana meninggalkan ruang lingkup yang terkait secara otomatis memanggil destruktor / melepaskan variabel-variabel ini (maka dari itu kualifikasi automatic). Namun untuk variabel tumpukan, pola yang sangat berguna ini rusak, karena Anda masih dipaksa untuk memanggil secara eksplisit deleteuntuk menjalankan destruktor, dan jika Anda lupa melakukannya, Anda masih akan tergigit oleh apa yang coba dipecahkan oleh RAII; dalam konteks heap-dialokasikan variabel, maka, C ++ memberikan manfaat terbatas atas C ( deletevsfree()) saat menggabungkan konstruksi dengan inisialisasi, yang berdampak negatif dalam hal berikut:

Membangun sistem objek untuk permainan / simulasi dalam C sangat disarankan karena akan memberikan banyak cahaya pada keterbatasan RAII dan pola OO-sentris lainnya melalui pemahaman yang lebih dalam tentang asumsi yang dibuat oleh C ++ dan kemudian bahasa OO klasik. (ingat bahwa C ++ dimulai sebagai sistem OO yang dibangun di C).

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.