Mẫu thiết kế cho hành vi đa hình trong khi cho phép tách thư viện


10

Hãy nói rằng tôi có một hệ thống phân cấp các Itemlớp : Rectangle, Circle, Triangle. Tôi muốn có thể vẽ chúng, vì vậy khả năng đầu tiên của tôi là thêm một Draw()phương thức ảo vào mỗi phương thức:

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

Tuy nhiên, tôi muốn phân chia chức năng vẽ thành một thư viện Draw riêng trong khi thư viện Core chỉ chứa các biểu diễn cơ bản. Có một vài khả năng tôi có thể nghĩ ra:

1 - A DrawManagerlấy danh sách Items và phải sử dụng dynamic_cast<>để tìm ra việc cần làm:

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

Điều này không lý tưởng vì nó phụ thuộc vào RTTI và buộc một lớp phải nhận thức được tất cả các mục trong hệ thống phân cấp.

2 - Cách tiếp cận khác là trì hoãn việc rút ra trách nhiệm đối với ItemDrawerhệ thống phân cấp ( RectangleDrawer, v.v.):

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

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

Điều này đạt được sự phân tách mối quan tâm giữa biểu diễn cơ sở của Vật phẩm và mã để vẽ. Vấn đề là các lớp Item phụ thuộc vào các lớp vẽ.

Làm cách nào tôi có thể tách mã bản vẽ này thành một thư viện riêng? Là giải pháp cho các Mục để trả về một lớp nhà máy của một số mô tả? Tuy nhiên, làm thế nào để xác định điều này để thư viện Core không phụ thuộc vào thư viện Draw?

Câu trả lời:


3

Hãy nhìn vào mô hình khách truy cập .

Mục đích của nó là tách một thuật toán (trong bản vẽ trường hợp của bạn) khỏi cấu trúc đối tượng mà nó vận hành (các mục).

Vì vậy, một ví dụ đơn giản cho vấn đề của bạn sẽ là:

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

Thú vị - Tôi chưa bao giờ nghĩ đến việc sử dụng một khách truy cập bị quá tải để đạt được tính đa hình. Điều này sẽ quá tải chính xác trong thời gian chạy mặc dù?
the_mandrill

Có nó sẽ quá tải chính xác. Xem câu trả lời được chỉnh sửa
hotpotato

Tôi có thể thấy rằng điều này hoạt động. Mặc dù thật xấu hổ khi mỗi lớp dẫn xuất phải thực hiện visit()phương thức này.
the_mandrill

Điều này thôi thúc tôi thực hiện tìm kiếm thêm một chút và giờ tôi đã tìm thấy việc triển khai mẫu Khách truy cập của Loki dường như chính xác là những gì tôi đang tìm kiếm! Tôi đã quen thuộc với mô hình khách truy cập về việc truy cập các nút trong cây, nhưng đã không nghĩ về nó theo cách trừu tượng hơn.
the_mandrill

2

Sự phân chia mà bạn mô tả - thành hai hệ thống phân cấp song song - về cơ bản là mô hình cầu nối .

Bạn lo lắng rằng nó làm cho sự trừu tượng hóa tinh tế (Hình chữ nhật, v.v.) phụ thuộc vào việc triển khai (Hình chữ nhật), nhưng tôi không chắc làm thế nào bạn có thể tránh điều đó hoàn toàn.

Bạn chắc chắn có thể cung cấp một nhà máy trừu tượng để phá vỡ sự phụ thuộc đó, nhưng bạn vẫn cần một số cách để nói với nhà máy đó sẽ tạo lớp con nào, và lớp con đó vẫn phụ thuộc vào việc triển khai được tinh chỉnh cụ thể. Đó là, ngay cả khi Hình chữ nhật không phụ thuộc vào Hình chữ nhật, nó vẫn cần một cách để bảo nhà máy xây dựng một hình chữ nhật, và chính Hình chữ nhật vẫn phụ thuộc vào Hình chữ nhật.

Cũng đáng để hỏi liệu đây có phải là một sự trừu tượng hữu ích. Việc vẽ một hình chữ nhật có khó đến mức đáng để đặt vào một lớp differnt hay bạn thực sự đang cố gắng trừu tượng hóa thư viện đồ họa?


Tôi nghĩ rằng tôi đã thấy mô hình đó trước khi có hệ thống phân cấp song song - cảm ơn vì đã khiến tôi phải suy nghĩ về điều đó. Tôi mặc dù muốn trừu tượng thư viện đồ họa. Tôi không muốn thư viện lõi phải phụ thuộc vào thư viện đồ họa. Giả sử ứng dụng lõi quản lý các đối tượng hình dạng và thư viện để hiển thị chúng là một tiện ích bổ sung. Hãy nhớ rằng đây thực sự không phải là một ứng dụng để vẽ hình chữ nhật - tôi chỉ sử dụng nó như một phép ẩn dụ.
the_mandrill

Những gì tôi nghĩ là RectangleDrawer, CircleDrawervv có thể không phải là cách hữu ích nhất để trừu tượng thư viện đồ họa. Thay vào đó, nếu bạn phơi bày một tập hợp các nguyên thủy thông qua một giao diện trừu tượng thông thường, bạn vẫn phá vỡ sự phụ thuộc.
Vô dụng

1

Có một cái nhìn về ngữ nghĩa giá trị và đa hình dựa trên khái niệm .

Công việc này đã thay đổi cách tôi nhìn vào đa hình. Chúng tôi đã sử dụng hệ thống này để ảnh hưởng lớn trong các tình huống lặp đi lặp lại.


Điều này có vẻ hấp dẫn - có vẻ như nó đang đi theo hướng tôi muốn
the_mandrill

1
Liên kết bị hỏng. Đây là lý do tại sao các câu trả lời về cơ bản chỉ liên kết được khuyến khích.
ajb

0

Lý do tại sao bạn gặp vấn đề là vì các đối tượng không nên tự vẽ. Vẽ một cái gì đó chính xác phụ thuộc vào một tỷ mảnh trạng thái và Hình chữ nhật sẽ không có bất kỳ mảnh nào trong số đó. Bạn nên có một đối tượng đồ họa trung tâm đại diện cho trạng thái cốt lõi như backbuffer / sâu đệm / shader / etc, và sau đó cung cấp cho nó một phương thức Draw ().


Tôi đồng ý rằng các đối tượng không nên tự vẽ, do đó mong muốn chuyển khả năng đó sang một lớp khác để đạt được sự tách biệt các mối quan tâm. Giả sử các lớp này là Doghoặc Cat- đối tượng chịu trách nhiệm vẽ chúng phức tạp hơn nhiều so với vẽ hình chữ nhật.
the_mandrill
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.