รูปแบบการออกแบบสำหรับพฤติกรรม polymorphic ในขณะที่อนุญาตให้แยกไลบรารี


10

สมมติว่าผมมีลำดับชั้นของชั้นเรียน:Item Rectangle, Circle, Triangleฉันต้องการที่จะสามารถวาดพวกเขาดังนั้นความเป็นไปได้ครั้งแรกของฉันคือการเพิ่มDraw()วิธีเสมือนในแต่ละ:

class Item {
public:
   virtual ~Item();
   virtual void Draw() =0; 
};

อย่างไรก็ตามฉันต้องการแยกฟังก์ชั่นการวาดภาพเป็นห้องสมุดวาดแยกในขณะที่ห้องสมุด Core มีเพียงการแสดงขั้นพื้นฐาน มีความเป็นไปได้สองอย่างที่ฉันคิดได้:

1 - A DrawManagerที่รับรายการItems และต้องใช้dynamic_cast<>เพื่อระบุสิ่งที่ต้องทำ:

class DrawManager {
  void draw(ItemList& items) {
    FOREACH(Item* item, items) {
       if (dynamic_cast<Rectangle*>(item)) {
          drawRectangle();
       } else if (dynamic_cast<Circle*>(item)) {
          drawCircle();
       } ....
    }
  }
};

สิ่งนี้ไม่เหมาะเนื่องจากอาศัย RTTI และบังคับให้คลาสหนึ่งต้องระวังรายการทั้งหมดในลำดับชั้น

2 - วิธีอื่นคือการเลื่อนความรับผิดชอบไปยังItemDrawerลำดับชั้น ( RectangleDrawerฯลฯ ):

class Item {
   virtual Drawer* GetDrawer() =0;
}

class Rectangle : public Item {
public:
   virtual Drawer* GetDrawer() {return new RectangleDrawer(this); }
}

สิ่งนี้ทำให้การแยกข้อกังวลระหว่างการแสดงพื้นฐานของรายการและรหัสสำหรับการวาดภาพ แม้ว่าปัญหาคือคลาสไอเท็มขึ้นอยู่กับคลาสรูปวาด

ฉันจะแยกรหัสรูปวาดนี้ออกเป็นห้องสมุดแยกได้อย่างไร วิธีแก้ปัญหาสำหรับรายการที่จะส่งคืนคลาสโรงงานของคำอธิบายบางอย่างหรือไม่? อย่างไรก็ตามสามารถกำหนดสิ่งนี้ได้อย่างไรเพื่อให้ไลบรารี Core ไม่ได้ขึ้นอยู่กับไลบรารี Draw

คำตอบ:


3

ลองดูที่เป็นรูปแบบของผู้เข้าชม

ความตั้งใจของมันคือการแยกอัลกอริทึม (ในรูปวาดกรณีของคุณ) จากโครงสร้างวัตถุที่ทำงาน (รายการ)

ตัวอย่างง่ายๆสำหรับปัญหาของคุณคือ:

class Item
{
public:
    virtual void visit(Visitable v)
    {
        v.accept(*this)
    }
};

class Rectangle : public Item
{
public:
    virtual void visit(Visitable v)
    {
        v.accept(*this)
    }
};

class Circle : public Item
{
public:
    virtual void visit(Visitable v)
    {
        v.accept(*this)
    }
};


class Visitable
{
public:
    virtual void accept(Rectangle& r);
    virtual void accept(Circle& c);
};

class Drawer : public Visitable
{
public:
    void accept(Rectangle& r)
    {
        // draw rectangle
    }   
    void accept(Circle& c)
    {
        // draw circle
    }
};


int main()
{
    Item* i1 = new Circle;
    Item* i2 = new Rectangle;

    Drawer d;
    i1->visit(d); // Draw circle
    i2->visit(d); // Draw rectangle

    return 1;
}

ที่น่าสนใจ - ฉันไม่เคยคิดถึงการใช้ผู้เยี่ยมชมที่มีการโหลดมากเกินไปเพื่อให้เกิดความหลากหลาย โอเวอร์โหลดนี้ถูกต้องหรือไม่ในขณะใช้งานจริง
the_mandrill

ใช่มันจะโอเวอร์โหลดอย่างถูกต้อง ดูคำตอบที่แก้ไข
hotpotato

ฉันเห็นว่ามันใช้งานได้ มันเป็นความอัปยศแม้ว่าแต่ละคลาสที่ได้รับจะต้องใช้visit()วิธีการ
the_mandrill

สิ่งนี้กระตุ้นให้ฉันทำการค้นหาอีกเล็กน้อยและตอนนี้ฉันก็พบว่าการนำรูปแบบการเยี่ยมชมของ Loki มาใช้ซึ่งดูเหมือนจะเป็นสิ่งที่ฉันกำลังมองหา! ฉันคุ้นเคยกับรูปแบบของผู้เข้าชมในแง่ของการเยี่ยมชมโหนดในต้นไม้ แต่ไม่เคยคิดมาก่อนเลย
the_mandrill

2

แยกที่คุณอธิบาย - เป็นสองลำดับชั้นขนาน - เป็นหลักรูปแบบสะพาน

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

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

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


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

สิ่งที่ฉันคิดคือว่าRectangleDrawer, CircleDrawerฯลฯ ไม่อาจจะเป็นวิธีที่มีประโยชน์มากที่สุดที่จะเป็นนามธรรมห้องสมุดกราฟิก หากคุณเปิดเผยชุดของไพรเมอร์ผ่านอินเทอร์เฟซนามธรรมทั่วไปคุณก็ยังคงต้องพึ่งการพึ่งพา
ไร้ประโยชน์

1

มีลักษณะที่คุ้มค่าและความหมายแนวคิดตามความแตกต่าง

งานนี้เปลี่ยนวิธีที่ฉันดูความหลากหลาย เราได้ใช้ระบบนี้เพื่อส่งผลกระทบอย่างมากในสถานการณ์ซ้ำ


สิ่งนี้ดูน่าสนใจ - ดูเหมือนว่ากำลังมุ่งหน้าไปในทิศทางที่ฉันต้องการ
the_mandrill

1
ลิงก์เสีย นี่คือเหตุผลที่คำตอบที่เชื่อมโยงอย่างเดียวเป็นสิ่งที่ทำให้หมดกำลังใจ
ajb

0

สาเหตุที่คุณมีปัญหาคือเพราะวัตถุไม่ควรวาดตัวเอง การวาดบางสิ่งบางอย่างอย่างถูกต้องขึ้นอยู่กับรัฐพันล้านชิ้นส่วนและสี่เหลี่ยมผืนผ้าจะไม่มีชิ้นส่วนเหล่านั้น คุณควรมีวัตถุกราฟิกส่วนกลางซึ่งแสดงถึงสถานะแกนกลางเช่น backbuffer / buffer ความลึก / shaders / ฯลฯ และจากนั้นให้วิธีการ Draw ()


ฉันยอมรับว่าวัตถุไม่ควรวาดตัวเองดังนั้นความปรารถนาที่จะย้ายความสามารถนั้นไปสู่ชั้นเรียนอื่นเพื่อให้เกิดความกังวลแยกออกจากกัน สมมติว่าคลาสเหล่านี้มีDogหรือCat- วัตถุที่รับผิดชอบในการวาดมีความซับซ้อนมากกว่าการวาดรูปสี่เหลี่ยมผืนผ้า
the_mandrill
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.