Desain berbasis komponen: menangani interaksi objek


9

Saya tidak yakin bagaimana tepatnya objek melakukan sesuatu pada objek lain dalam desain berbasis komponen.

Katakanlah saya punya Objkelas. Saya lakukan:

Obj obj;
obj.add(new Position());
obj.add(new Physics());

Bagaimana saya dapat memiliki objek lain yang tidak hanya menggerakkan bola tetapi juga menerapkan fisika tersebut. Saya tidak mencari detail implementasi tetapi secara abstrak bagaimana objek berkomunikasi. Dalam desain berbasis entitas, Anda mungkin hanya memiliki:

obj1.emitForceOn(obj2,5.0,0.0,0.0);

Artikel atau penjelasan apa pun untuk mendapatkan pemahaman yang lebih baik tentang desain yang digerakkan oleh komponen dan bagaimana melakukan hal-hal dasar akan sangat membantu.

Jawaban:


10

Itu biasanya dilakukan dengan menggunakan pesan. Anda dapat menemukan banyak detail dalam pertanyaan lain di situs ini, seperti di sini atau di sana .

Untuk menjawab contoh spesifik Anda, cara untuk pergi adalah dengan mendefinisikan Messagekelas kecil yang dapat diproses objek Anda, misalnya:

struct Message
{
    Message(const Objt& sender, const std::string& msg)
        : m_sender(&sender)
        , m_msg(msg) {}
    const Obj* m_sender;
    std::string m_msg;
};

void Obj::Process(const Message& msg)
{
    for (int i=0; i<m_components.size(); ++i)
    {
        // let components do some stuff with msg
        m_components[i].Process(msg);
    }
}

Dengan cara ini Anda tidak "mencemari" Objantarmuka kelas Anda dengan metode terkait komponen. Beberapa komponen dapat memilih untuk memproses pesan, beberapa mungkin mengabaikannya.

Anda bisa mulai dengan memanggil metode ini langsung dari objek lain:

Message msg(obj1, "EmitForce(5.0,0.0,0.0)");
obj2.ProcessMessage(msg);

Dalam kasus ini, obj2's Physicsakan memilih pesan, dan melakukan apa pengolahan yang perlu dilakukan. Ketika selesai, itu akan:

  • Kirim pesan "SetPosition" ke diri sendiri, bahwa Positionkomponen akan memilih;
  • Atau langsung mengakses Positionkomponen untuk modifikasi (sangat salah untuk desain berbasis komponen murni, karena Anda tidak dapat mengasumsikan setiap objek memiliki Positionkomponen, tetapi Positionkomponen itu bisa menjadi persyaratan Physics).

Biasanya merupakan ide bagus untuk menunda pemrosesan pesan yang sebenarnya ke pembaruan komponen berikutnya . Memprosesnya dengan segera dapat berarti mengirim pesan ke komponen lain dari objek lain, jadi mengirim hanya satu pesan dapat dengan cepat berarti tumpukan spageti yang tidak dapat dipisahkan.

Anda mungkin harus menggunakan sistem yang lebih maju di kemudian hari: antrian pesan tidak sinkron, mengirim pesan ke sekelompok objek, mendaftar per komponen / tidak mendaftar dari pesan, dll

The Messagekelas dapat menjadi wadah generik untuk string sederhana seperti yang ditunjukkan di atas, tetapi pengolahan string di runtime tidak benar-benar efisien. Anda bisa mencari wadah nilai generik: string, integer, floats ... Dengan nama atau lebih baik lagi, ID, untuk membedakan berbagai jenis pesan. Atau Anda juga bisa mendapatkan kelas dasar agar sesuai dengan kebutuhan spesifik. Dalam kasus Anda, Anda dapat membayangkan sebuah EmitForceMessageyang berasal dari Messagedan menambahkan vektor gaya yang diinginkan -tapi waspadalah terhadap biaya runtime RTTI jika Anda melakukannya.


3
Saya tidak akan khawatir tentang "tidak murni" secara langsung mengakses komponen. Komponen digunakan untuk melayani kebutuhan fungsional dan desain, bukan akademisi. Anda ingin memeriksa apakah ada komponen (mis., Periksa nilai balik bukan nol untuk panggilan komponen get).
Sean Middleditch

Saya selalu menganggapnya sebagai yang terakhir Anda katakan, menggunakan RTTI tetapi begitu banyak orang mengatakan begitu banyak hal buruk tentang RTTI
jmasterx

@SeanMiddleditch Tentu, saya akan melakukannya dengan cara ini, hanya menyebutkan bahwa untuk memperjelas bahwa Anda harus selalu memeriksa ulang apa yang Anda lakukan ketika mengakses komponen lain dari entitas yang sama.
Laurent Couvidou

@ Moilo RTTI yang diimplementasikan oleh kompiler dan dynamic_cast dapat menjadi hambatan, tapi saya tidak khawatir tentang itu untuk saat ini. Anda masih bisa mengoptimalkan ini nanti jika itu menjadi masalah. Pengidentifikasi kelas berbasis CRC bekerja seperti pesona.
Laurent Couvidou

´template <typename T> uint32_t class_id () {static uint32_t v; return (uint32_t) & v; } ´ - tidak perlu RTTI.
arul

3

Apa yang saya lakukan untuk menyelesaikan masalah yang serupa dengan yang Anda perlihatkan, adalah menambahkan beberapa penangan komponen tertentu dan menambahkan semacam sistem resolusi acara.

Jadi, dalam kasus objek "Fisika" Anda, ketika itu diinisialisasi itu akan menambah dirinya sendiri ke manajer pusat objek Fisika. Dalam lingkaran permainan, manajer semacam ini memiliki langkah pembaruan sendiri, jadi ketika Manajer Fisika ini diperbarui, ia menghitung semua interaksi fisika dan menambahkannya ke dalam antrian acara.

Setelah Anda menghasilkan semua acara Anda, Anda dapat menyelesaikan antrian acara Anda hanya dengan memeriksa apa yang terjadi dan mengambil tindakan sesuai, dalam kasus Anda, harus ada acara yang mengatakan objek A dan B berinteraksi entah bagaimana, sehingga Anda memanggil metode emitForceOn Anda.

Kelebihan metode ini:

  • Secara konseptual, sangat mudah diikuti.
  • Memberi Anda ruang untuk optimasi spesifik seperti menggunakan quadtress atau apa pun yang Anda perlukan.
  • Itu akhirnya benar-benar "plug and play". Objek dengan fisika tidak berinteraksi dengan objek non-fisika karena tidak ada untuk manajer.

Cons:

  • Anda berakhir dengan banyak referensi bergerak, sehingga dapat menjadi sedikit berantakan untuk membersihkan semuanya dengan benar jika Anda tidak hati-hati (dari komponen Anda ke pemilik komponen, dari manajer ke komponen, dari acara ke peserta, dll. ).
  • Anda harus menaruh perhatian khusus pada urutan di mana Anda menyelesaikan semuanya. Saya kira itu bukan kasus Anda, tetapi saya menghadapi lebih dari satu loop tak terbatas di mana sebuah acara membuat acara lain dan saya hanya menambahkannya ke antrian acara secara langsung.

Saya harap ini membantu.

PS: Jika seseorang memiliki cara yang lebih bersih / lebih baik untuk menyelesaikan ini, saya benar-benar ingin mendengarnya.


1
obj->Message( "Physics.EmitForce 0.0 1.1 2.2" );
// and some variations such as...
obj->Message( "Physics.EmitForce", "0.0 1.1 2.2" );
obj->Message( "Physics", "EmitForce", "0.0 1.1 2.2" );

Beberapa hal yang perlu diperhatikan pada desain ini:

  • Nama komponen adalah parameter pertama - ini untuk menghindari terlalu banyak kode yang berfungsi pada pesan - kita tidak bisa tahu komponen apa yang dipicu pesan - dan kami tidak ingin semuanya mengunyah pesan dengan kegagalan 90% tingkat yang mengkonversi ke banyak cabang dan strcmp yang tidak perlu .
  • Nama pesan adalah parameter kedua.
  • Titik pertama (di # 1 dan # 2) tidak perlu, hanya untuk membuat membaca lebih mudah (untuk orang, bukan komputer).
  • Ini sscanf, iostream, Anda-nama-itu kompatibel. Tidak ada gula sintaksis yang tidak melakukan apa pun untuk menyederhanakan pemrosesan pesan.
  • Parameter satu string: melewatkan tipe asli tidak lebih murah dalam hal persyaratan memori karena Anda harus mendukung sejumlah parameter dengan tipe yang relatif tidak dikenal.
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.