เรียกเมธอดคลาสที่ได้รับจากลูปรายการตัวชี้ฐาน (OOD)

ปัญหา

ฉันประสบปัญหาง่ายๆ แม้ว่าฉันไม่สามารถหา OOD ที่เหมาะสมสำหรับมันได้

สิ่งที่ฉันมี:

  • คลาสพื้นฐาน
  • คลาสย่อยเพิ่มวิธีการใหม่ foo()
  • รายการพอยน์เตอร์ไปยังอินสแตนซ์คลาสฐาน

สิ่งที่ฉันต้องการ:

ฉันต้องวนซ้ำรายการนี้และเรียก foo() สำหรับอ็อบเจ็กต์ที่รองรับเมธอดนี้ เช่น อ็อบเจ็กต์ของ (หรือมาจาก) คลาสย่อยที่กล่าวมาข้างต้น หรือพูดโดยทั่วไป ฉันต้องการการเข้าถึงคลาสย่อยแบบ "ไม่มีกลิ่น" แบบโพลีมอร์ฟิกผ่านรายการพอยน์เตอร์ไปยังคลาสพื้นฐาน

รหัสตัวอย่าง

class Entity {
    // ...
    // This class contains methods also needed by subclasses.
};

class SaveableEntity : public Entity {
public:
    virtual void save() = 0;
};

// SaveableEntity has multiple subclasses with specific save() implementations.

std::vector<Entity *> list;
for (Entity *entity : list) {
    // Here I need to save() the descendants of a SaveableEntity type.
}

ฉันเกิดไอเดียบางอย่างขึ้นมา แต่ก็ไม่มีไอเดียใดที่เหมาะกับฉันเลย นี่คือบางส่วนของพวกเขา:

วิธีที่ 1: dynamic_cast

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

std::vector<Entity *> list;
for (Entity *entity : list) {
    auto saveable = dynamic_cast<SaveableEntity *>(entity);
    if (saveable) {
        saveable->save();
    }
}

อย่างไรก็ตาม การใช้ dynamic_cast ดูเหมือน OOD ที่ไม่ดีในสถานการณ์นี้ (แก้ไขฉันหากฉันผิด) นอกจากนี้ วิธีการนี้อาจนำไปสู่การละเมิด LSP ได้อย่างง่ายดาย

วิธีที่ 2: ย้าย save() ไปยังคลาสพื้นฐาน

ฉันสามารถลบ SaveableEntity และย้ายวิธี save() ไปที่ฐาน Entity อย่างไรก็ตาม สิ่งนี้ทำให้เรานำวิธีการจำลองมาใช้:

class Entity {
    virtual void save() {
        // Do nothing, override in subclasses
    }
};

สิ่งนี้จะกำจัดการใช้งาน dynamic_cast แต่วิธีจำลองยังคงดูไม่ถูกต้อง: ตอนนี้คลาสพื้นฐานเก็บข้อมูล (วิธี save()) ที่ไม่เกี่ยวข้องโดยสิ้นเชิง

วิธีที่ 3: ใช้รูปแบบการออกแบบ

  • รูปแบบกลยุทธ์: คลาส SaveStrategy และคลาสย่อยเช่น NoSaveStrategy, SomeSaveStrategy, SomeOtherSaveStrategy ฯลฯ อีกครั้ง การมีอยู่ของ NoSaveStrategy นำเรากลับไปสู่ข้อบกพร่องของวิธีการก่อนหน้านี้: คลาสพื้นฐานต้องทราบรายละเอียดเฉพาะเกี่ยวกับ คลาสย่อยซึ่งดูเหมือนเป็นการออกแบบที่ไม่ดี
  • รูปแบบ พร็อกซี หรือ มัณฑนากร สามารถสรุป dynamic_cast ได้อย่างง่ายดาย อย่างไรก็ตาม การดำเนินการนี้จะซ่อนเฉพาะโค้ดที่ไม่ต้องการเท่านั้น โดยไม่กำจัดการออกแบบที่ไม่ดีออกไป
  • เพิ่มเลเยอร์การแต่งเพลงทับการสืบทอด และอื่น ๆ...

คำถาม

บางทีฉันอาจขาดวิธีแก้ปัญหาที่ชัดเจน หรือวิธีที่อธิบายไว้ (1 หรือ 2) อาจไม่แย่และมีกลิ่นเหม็นในบริบทนี้เท่าที่ฉันเห็น

แล้วแนวทางการออกแบบแบบไหนที่เหมาะกับสถานการณ์เช่นนี้?


person kefir500    schedule 22.11.2018    source แหล่งที่มา
comment
คุณยังคงสามารถใช้ฟังก์ชันเสมือนจริงได้ นี่จะทำให้มันเป็นพฤติกรรมเริ่มต้นไม่มากก็น้อย ฉันไม่แน่ใจเกี่ยวกับกลิ่นเหม็น แต่ :-/   -  person MauriceRandomNumber    schedule 22.11.2018
comment
ดูปัญหาของคุณภายใต้มุมนี้: อินเทอร์เฟซคลาสพื้นฐานไม่รองรับ foo และคุณต้องการทำผ่านอินเทอร์เฟซดังกล่าว (เข้าถึงวัตถุจริงผ่านตัวชี้คลาสพื้นฐาน) บางสิ่ง ( foo ) ที่ไม่รองรับ   -  person Andrew Kashpur    schedule 22.11.2018


คำตอบ (2)


มีโซลูชัน #4 สนับสนุนโดยการเขียนโปรแกรมเชิงข้อมูล (มีการพูดคุยที่ยอดเยี่ยมใน cppcon 2018 มีอยู่ใน youtube): มีสองรายการ รายการหนึ่งสำหรับ SavableEntitys ทั้งหมด และอีกรายการสำหรับ Entitys ที่ไม่สามารถบันทึกได้

ตอนนี้ คุณวนซ้ำรายการแรกและ ->save() รายการเหล่านั้น

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

person YSC    schedule 22.11.2018

แนวคิดแรกของฉันคือการเพิ่มคลาสฐานด้วยเมธอด virtual bool trySave() ค่าเริ่มต้นอาจเป็น return false; ในขณะที่ SaveableEntity ให้การแทนที่นี้:

class SaveableEntity : public Entity {
public:
    virtual bool trySave() final override
    {
        save();
        return true;
    }

    // You could also make this protected.
    virtual void save() = 0;
};

สิ่งนี้จะเหมาะสมกว่าข้อเสนอแนะของ @ YSC สำหรับกรณีเฉพาะของคุณหรือไม่นั้นเป็นสิ่งที่คุณต้องตัดสินใจด้วยตัวเอง คล้ายกับการย้าย save() เข้าสู่คลาสพื้นฐาน แต่ผู้ใช้สับสนน้อยกว่ามาก

person Max Langhof    schedule 22.11.2018
comment
ขอบคุณสำหรับคำตอบอย่างไรก็ตามแม้ว่าจะปรับปรุงวิธีที่ # 2 แต่ก็ยังไม่สามารถแก้ปัญหาคลาสพื้นฐานที่รู้ทุกอย่างซึ่ง (ตามหลักการแล้ว) ไม่ควรรู้อะไรเลยเกี่ยวกับคลาสที่ได้รับ - person kefir500; 26.11.2018