Bileşen tabanlı tasarım: nesne etkileşimini işleme


9

Bileşen tabanlı bir tasarımda nesnelerin diğer nesnelere nasıl bir şeyler yaptığını tam olarak bilmiyorum.

Diyelim ki bir Objdersim var. Yaparım:

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

Daha sonra başka bir nesneye nasıl sadece topu hareket ettirmekle kalmayıp bu fiziği de uygulayabilirim. Uygulama ayrıntılarını değil, nesnelerin nasıl iletişim kurduğunu soyut olarak arıyorum. Varlık tabanlı bir tasarımda şunlara sahip olabilirsiniz:

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

Bileşen odaklı bir tasarımı ve temel şeylerin nasıl yapılacağını daha iyi kavramak için herhangi bir makale veya açıklama gerçekten yararlı olacaktır.

Yanıtlar:


10

Bu genellikle mesajlar kullanılarak yapılır. Bu sitedeki diğer sorularda, burada veya orada olduğu gibi birçok ayrıntı bulabilirsiniz .

Özel örneğinizi yanıtlamak için, gidilecek bir yol Messagenesnelerinizin işleyebileceği küçük bir sınıf tanımlamaktır , örneğin:

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);
    }
}

Bu şekilde, Objsınıf arayüzünü bileşenle ilgili yöntemlerle "kirletmezsiniz" . Bazı bileşenler mesajı işlemeyi seçebilir, bazıları bunu görmezden gelebilir.

Bu yöntemi doğrudan başka bir nesneden çağırarak başlayabilirsiniz:

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

Bu durumda, obj2'ın Physicsmesajı almak ve işlemek bunu yapması gereken neyse yapacağız. Tamamlandığında, ya:

  • Kendine, Positionbileşenin seçeceği bir "SetPosition" iletisi gönderin ;
  • Ya da Positionmodifikasyonlar için bileşene doğrudan erişin (her nesnenin bir Positionbileşeni olduğunu varsayamayacağınız, ancak Positionbileşenin bir gereksinimi olabilir Physics) saf bileşen tabanlı bir tasarım için oldukça yanlıştır .

İletinin gerçek işlenmesini bir sonraki bileşenin güncelleştirmesine ertelemek genellikle iyi bir fikirdir . Hemen işlenmesi, diğer nesnelerin diğer bileşenlerine mesaj göndermek anlamına gelebilir, bu nedenle sadece bir mesaj göndermek hızla ayrılmaz bir spagetti yığını anlamına gelebilir.

Muhtemelen ileride daha gelişmiş bir sisteme gitmeniz gerekecek: eşzamansız mesaj kuyrukları, nesne grubuna mesaj gönderme, bileşen başına kayıt / mesajlardan kayıt silme vb.

MessageYukarıda gösterilen, ancak zamanında dizeleri işleme gerçekten verimli olmadığı için sınıf basit dize için genel bir kap olabilir. Farklı değer türlerini ayırt etmek için genel değerler içeren bir kapsayıcı için gidebilirsiniz: dizeler, tamsayılar, kayanlar ... Bir ad veya daha iyisi ile bir kimlik. Veya belirli ihtiyaçlara uyacak bir temel sınıf da türetebilirsiniz. Sizin durumunuzda, istenen kuvvet vektöründen EmitForceMessagetüreyen Messageve ekleyen bir hayal edebilirsiniz - ancak bunu yaparsanız RTTI'nin çalışma zamanı maliyetine dikkat edin .


3
Bileşenlere doğrudan erişmenin "saf olmayanlığı" konusunda endişe etmem. Bileşenler, akademik çevreye değil, işlevsel ve tasarım ihtiyaçlarına hizmet etmek için kullanılır. Bir bileşenin var olup olmadığını kontrol etmek istersiniz (örn. Get bileşen çağrısı için dönüş değerinin boş olmadığını kontrol edin).
Sean Middleditch

RTTI kullanarak her zaman en son söylediğin gibi düşündüm ama pek çok insan RTTI hakkında çok kötü şeyler söyledi
jmasterx

@SeanMiddleditch Elbette, bu şekilde yapardım, sadece aynı varlığın diğer bileşenlerine erişirken ne yaptığınızı her zaman iki kez kontrol etmeniz gerektiğini açıkça belirtmek için söylüyorum.
Laurent Couvidou

Derleyici-uygulanmaktadır RTTI ve onun @Milo dynamic_cast edebilir bir darboğaz haline, ama şimdilik bu konuda endişe woudlnt. Bir sorun haline gelirse bunu daha sonra optimize edebilirsiniz. CRC tabanlı sınıf tanımlayıcıları bir cazibe gibi çalışır.
Laurent Couvidou

´template <typename T> uint32_t class_id () {statik uint32_t v; dönüş (uint32_t) & v; } ´ - RTTI gerekmez.
arul

3

Gösterdiğinize benzer bir sorunu çözmek için yaptığım şey, bazı belirli bileşen işleyicileri eklemek ve bir tür olay çözümleme sistemi eklemektir.

Yani, "Fizik" nesneniz söz konusu olduğunda, bu başlatıldığında kendisini Fizik nesnelerinin merkezi yöneticisine ekleyecektir. Oyun döngüsünde, bu tür yöneticilerin kendi güncelleme adımları vardır, bu nedenle bu PhysicsManager güncellendiğinde tüm fizik etkileşimlerini hesaplar ve bunları bir olay kuyruğuna ekler.

Tüm etkinliklerinizi oluşturduktan sonra, olay kuyruğunuzu basitçe olup biteni kontrol ederek ve eylemleri kaydederek çözebilirsiniz, sizin durumunuzda A ve B nesnesinin bir şekilde etkileştiğini söyleyen bir olay olmalıdır, böylece emitForceOn yönteminizi çağırırsınız.

Bu yöntemin artıları:

  • Kavramsal olarak, takip etmek gerçekten basittir.
  • Quadress ya da ihtiyacınız olan her şeyi kullanmak gibi özel optimizasyonlara yer açar.
  • Sonunda gerçekten "tak ve çalıştır" oluyor. Fizikli nesneler, fizik olmayan nesnelerle etkileşime girmezler çünkü yönetici için mevcut değildirler.

Eksileri:

  • Çok sayıda referansla hareket edersiniz, bu yüzden dikkatli değilseniz her şeyi doğru bir şekilde temizlemek biraz karışık olabilir (bileşeninizden bileşen sahibine, yöneticiden bileşene, etkinlikten katılımcılara vb.) ).
  • Her şeyi çözdüğünüz sıraya özel bir düşünce koymak zorundasınız. Sanırım senin durumun değil, ama bir olayın başka bir olay yarattığı birden fazla sonsuz döngü ile karşı karşıya kaldım ve sadece olay sırasına doğrudan ekliyorum.

Umarım bu yardımcı olur.

Not: Birisi bunu çözmek için daha temiz / daha iyi bir yol varsa, gerçekten duymak istiyorum.


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" );

Bu tasarımda dikkat edilmesi gereken birkaç nokta:

  • Bileşenin adı ilk parametredir - bu, iletide çok fazla kod çalışmasını önlemek içindir - herhangi bir iletinin hangi bileşenleri tetikleyebileceğini bilemeyiz - ve hepsinin% 90 başarısızlık içeren bir iletiyi çiğnemesini istemiyoruz oranı çok gereksiz şube ve strcmp 's dönüştürür.
  • Mesajın adı ikinci parametredir.
  • İlk nokta (# 1 ve # 2'de) gerekli değildir, sadece okumayı kolaylaştırmak içindir (bilgisayarlar için değil, insanlar için).
  • Sscanf, iostream, ad-uyumludur. Mesajın işlenmesini basitleştirmek için hiçbir şey yapmayan sözdizimsel şeker yoktur.
  • Bir dize parametresi: yerel türlerin iletilmesi bellek gereksinimleri açısından daha ucuz değildir, çünkü nispeten bilinmeyen türde bilinmeyen sayıda parametreyi desteklemeniz gerekir.
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.