การออกแบบที่อิงส่วนประกอบ: การจัดการการโต้ตอบกับวัตถุ


9

ฉันไม่แน่ใจว่าวัตถุทำสิ่งต่าง ๆ อย่างไรกับวัตถุอื่น ๆ ในการออกแบบส่วนประกอบ

บอกว่าฉันมีObjชั้นเรียน ฉันทำ:

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

ฉันจะมีวัตถุอื่นได้อย่างไรไม่เพียง แต่เคลื่อนที่ลูกบอล แต่ใช้ฟิสิกส์เหล่านั้น ฉันไม่ได้กำลังมองหารายละเอียดการใช้งาน แต่ค่อนข้างเป็นนามธรรมว่าวัตถุสื่อสารกันอย่างไร ในการออกแบบตามเอนทิตีคุณอาจมี:

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

บทความหรือคำอธิบายใด ๆ เพื่อให้เข้าใจการออกแบบส่วนประกอบได้ดีขึ้นและวิธีการทำสิ่งพื้นฐานจะมีประโยชน์จริง ๆ

คำตอบ:


10

โดยปกติจะใช้ข้อความ คุณจะพบจำนวนของรายละเอียดในคำถามอื่น ๆ บนเว็บไซต์นี้เช่นที่นี่หรือมี

ในการตอบตัวอย่างเฉพาะของคุณวิธีที่จะไปคือการกำหนดMessageคลาสเล็ก ๆที่วัตถุของคุณสามารถประมวลผลได้เช่น:

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

วิธีนี้คุณจะไม่ "สร้างมลภาวะ" คุณObjเรียนอินเทอร์เฟซด้วยวิธีการที่เกี่ยวข้องกับส่วนประกอบ ส่วนประกอบบางอย่างสามารถเลือกที่จะประมวลผลข้อความบางอย่างก็อาจไม่สนใจ

คุณสามารถเริ่มด้วยการเรียกวิธีนี้โดยตรงจากวัตถุอื่น:

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

ในกรณีนี้obj2's Physicsจะรับข้อความและทำสิ่งที่การประมวลผลจะต้องทำ เมื่อเสร็จแล้วก็จะ:

  • ส่งข้อความ "SetPosition" ไปที่ตนเองซึ่งPositionส่วนประกอบนั้นจะเลือก;
  • หรือเข้าถึงPositionส่วนประกอบโดยตรงสำหรับการแก้ไข (ค่อนข้างผิดสำหรับการออกแบบโดยใช้องค์ประกอบบริสุทธิ์เนื่องจากคุณไม่สามารถสันนิษฐานได้ว่าทุกวัตถุมีPositionองค์ประกอบ แต่Positionองค์ประกอบอาจเป็นข้อกำหนดของPhysics)

โดยทั่วไปแล้วเป็นความคิดที่ดีที่จะชะลอการประมวลผลข้อความจริงไปเป็นการอัพเดทองค์ประกอบถัดไป การประมวลผลทันทีอาจหมายถึงการส่งข้อความไปยังส่วนประกอบอื่นของวัตถุอื่นดังนั้นการส่งเพียงข้อความเดียวอาจหมายถึงสปาเก็ตตี้ที่แยกไม่ออกอย่างรวดเร็ว

คุณอาจจะต้องใช้ระบบขั้นสูงเพิ่มเติมในภายหลังใน: คิวข้อความอะซิงโครนัสการส่งข้อความไปยังกลุ่มวัตถุการลงทะเบียน / ยกเลิกการลงทะเบียนจากข้อความต่อองค์ประกอบอื่น ๆ

Messageระดับสามารถเป็นภาชนะทั่วไปสำหรับสตริงที่เรียบง่ายตามที่ปรากฏข้างต้น แต่การประมวลผลสตริงที่รันไทม์ไม่ได้มีประสิทธิภาพจริงๆ คุณสามารถไปที่คอนเทนเนอร์ที่มีค่าทั่วไป: สตริงจำนวนเต็มลอย ... ด้วยชื่อหรือดีกว่ายังเป็น ID เพื่อแยกแยะข้อความประเภทต่างๆ หรือคุณสามารถหาคลาสพื้นฐานเพื่อให้เหมาะกับความต้องการเฉพาะได้ ในกรณีของคุณคุณสามารถจินตนาการได้EmitForceMessageว่าเกิดจากMessageและเพิ่มเวกเตอร์แรงที่ต้องการ แต่ระวังค่าใช้จ่ายรันไทม์ของRTTIถ้าคุณทำเช่นนั้น


3
ฉันจะไม่กังวลเกี่ยวกับ "ความไม่บริสุทธิ์" ของการเข้าถึงส่วนประกอบโดยตรง ส่วนประกอบถูกใช้เพื่อตอบสนองความต้องการด้านการใช้งานและการออกแบบไม่ใช่เชิงวิชาการ คุณต้องการตรวจสอบว่ามีองค์ประกอบอยู่แล้ว (เช่นการตรวจสอบค่าส่งคืนไม่ใช่ค่า null สำหรับการเรียกใช้คอมโพเนนต์)
Sean Middleditch

ฉันคิดเสมอตามที่คุณพูดครั้งล่าสุดโดยใช้ RTTI แต่มีคนมากมายที่พูดสิ่งเลวร้ายมากมายเกี่ยวกับ RTTI
jmasterx

@SeanMiddleditch แน่นอนว่าฉันจะทำแบบนี้โดยระบุว่าเพื่อให้ชัดเจนว่าคุณควรตรวจสอบสิ่งที่คุณทำอยู่เสมอเมื่อเข้าถึงส่วนประกอบอื่น ๆ ของเอนทิตีเดียวกัน
Laurent Couvidou

@Milo RTTI ที่คอมไพเลอร์นำมาใช้และdynamic_cast อาจกลายเป็นคอขวดได้ แต่ตอนนี้ฉันไม่กังวลเลย คุณยังสามารถเพิ่มประสิทธิภาพได้ในภายหลังหากเกิดปัญหา ตัวระบุคลาสตาม CRC ทำงานเหมือนมีเสน่ห์
Laurent Couvidou

emtemplate <typename T> uint32_t class_id () {static uint32_t v; ส่งคืน (uint32_t) & v; } ´- ไม่จำเป็นต้องใช้ RTTI
arul

3

สิ่งที่ฉันทำเพื่อแก้ไขปัญหาที่คล้ายกับสิ่งที่คุณแสดงคือการเพิ่มตัวจัดการส่วนประกอบเฉพาะและเพิ่มระบบการจำแนกเหตุการณ์บางประเภท

ดังนั้นในกรณีของวัตถุ "ฟิสิกส์" ของคุณเมื่อมีการเริ่มต้นมันจะเพิ่มตัวเองลงในผู้จัดการศูนย์กลางของฟิสิกส์ objetcs ในลูปเกมผู้จัดการประเภทนี้มีขั้นตอนการอัปเดตของตัวเองดังนั้นเมื่อ PhysicsManager นี้ได้รับการอัพเดตจะทำการคำนวณการโต้ตอบทางฟิสิกส์ทั้งหมดและเพิ่มพวกเขาลงในคิวเหตุการณ์

หลังจากที่คุณสร้างกิจกรรมทั้งหมดของคุณคุณสามารถแก้ไขคิวเหตุการณ์ของคุณเพียงตรวจสอบสิ่งที่เกิดขึ้นและดำเนินการตามลำดับในกรณีของคุณควรมีเหตุการณ์ที่บอกว่าวัตถุ A และ B โต้ตอบกันอย่างใดอย่างหนึ่งดังนั้นคุณจึงเรียกใช้

ข้อดีของวิธีนี้:

  • แนวคิดง่ายต่อการติดตาม
  • ให้คุณมีพื้นที่สำหรับการปรับแต่งเฉพาะเช่นการใช้ควอดเทรสหรือสิ่งที่คุณต้องการ
  • มันกลายเป็น "plug and play" จริงๆ วัตถุที่มีฟิสิกส์ไม่โต้ตอบกับวัตถุที่ไม่ใช่ฟิสิกส์เพราะไม่มีอยู่สำหรับผู้จัดการ

จุดด้อย:

  • คุณจบลงด้วยการอ้างอิงจำนวนมากย้ายไปรอบ ๆ ดังนั้นมันอาจจะยุ่งเล็กน้อยในการทำความสะอาดทุกอย่างอย่างถูกต้องหากคุณไม่ระวัง (จากองค์ประกอบของคุณไปยังเจ้าขององค์ประกอบจากผู้จัดการไปยังส่วนประกอบจากเหตุการณ์ไปยังผู้เข้าร่วม ฯลฯ )
  • คุณต้องใช้ความคิดพิเศษตามลำดับที่คุณแก้ไขทุกอย่าง ฉันเดาว่ามันไม่ใช่กรณีของคุณ แต่ฉันต้องเผชิญกับวงวนไม่สิ้นสุดหนึ่งเหตุการณ์ที่สร้างเหตุการณ์อีกเหตุการณ์หนึ่งและฉันเพิ่งเพิ่มไปยังคิวเหตุการณ์โดยตรง

ฉันหวังว่านี่จะช่วยได้.

PS: ถ้ามีคนที่มีวิธีที่ดีกว่า / ดีกว่าในการแก้ปัญหานี้ฉันอยากได้ยินมันจริงๆ


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

สิ่งเล็ก ๆ น้อย ๆ ที่ควรทราบในการออกแบบนี้:

  • ชื่อขององค์ประกอบคือพารามิเตอร์ตัวแรก - นี่คือการหลีกเลี่ยงการใช้รหัสในข้อความมากเกินไป - เราไม่สามารถรู้ได้ว่าส่วนประกอบใดที่ข้อความใดจะส่ง - และเราไม่ต้องการให้พวกเขาทั้งหมดเคี้ยวข้อความที่ล้มเหลว 90% อัตราที่แปลงเป็นจำนวนมากของสาขาที่ไม่จำเป็นและstrcmp 's
  • ชื่อของข้อความคือพารามิเตอร์ที่สอง
  • ไม่จำเป็นต้องใช้จุดแรก (ใน # 1 และ # 2) เพียงเพื่อทำให้การอ่านง่ายขึ้น (สำหรับคนไม่ใช่คอมพิวเตอร์)
  • มัน sscanf, iostream, คุณชื่อมันเข้ากันได้ ไม่มีน้ำตาลทรายที่ไม่ทำให้การประมวลผลข้อความง่ายขึ้น
  • พารามิเตอร์สตริงหนึ่ง: การผ่านชนิดเนทิฟนั้นไม่ถูกกว่าในแง่ของข้อกำหนดหน่วยความจำเนื่องจากคุณต้องรองรับพารามิเตอร์ที่ไม่ทราบจำนวนประเภทที่ไม่รู้จัก
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.