2012-05-23 43 views
7

Tôi có triển khai một Mẫu nhà nước nơi mỗi tiểu bang xử lý các sự kiện mà nó nhận được từ hàng đợi sự kiện. Do đó, lớp cơ sở State có phương thức ảo thuần túy là void handleEvent(const Event*). Sự kiện kế thừa lớp cơ sở Event nhưng mỗi sự kiện chứa dữ liệu của nó có thể thuộc một loại khác (ví dụ: int, string ... hoặc bất kỳ thứ gì). handleEvent phải xác định loại thời gian chạy của sự kiện đã nhận và sau đó thực hiện downcast để trích xuất dữ liệu sự kiện. Các sự kiện được tạo và lưu trữ động trong hàng đợi (vì vậy, hãy upcasting diễn ra tại đây ...).Làm thế nào để tránh downcast?

Tôi biết rằng downcasting là một dấu hiệu của một thiết kế xấu nhưng là nó có thể tránh nó trong trường hợp này? Tôi đang nghĩ đến Mô hình khách truy cập trong đó lớp cơ sở Nhà nước sẽ chứa trình xử lý ảo cho mỗi sự kiện nhưng sau đó một lần nữa sẽ cần phải diễn ra trong đoạn mã để loại bỏ sự kiện khỏi hàng đợi và chuyển nó tới trạng thái hiện tại. (Ít nhất trong trường hợp này lớn switch(eventID) sẽ chỉ ở một nơi ...). Mô hình khách truy cập là cách tốt nhất (thực hành tốt nhất) để tránh downcasting?

Đây là pseudo-code (tôi đi qua boost::shared_ptr trong ví dụ này nhưng downcasting xảy ra anyway):

enum EventID 
{ 
    EVENT_1, 
    EVENT_2, 
    ... 
}; 

class Event 
{ 
    EventID id; 
public: 
    Event(EventID id):id(id){} 
    EventID id() const {return id;} 
    virtual ~Event() = 0; 
}; 

class Event1 : public Event 
{ 
    int n; 
public: 
    Event1(int n):Event(EVENT_1), n(n){} 
    int getN() const {return n;} 
}; 

class Event2 : public Event 
{ 
    std::string s; 
public: 
    Event2(std::string s):Event(EVENT_2), s(s){} 
    std::string getS() const {return s;} 
}; 

typedef boost::shared_ptr<Event> EventPtr; 

class State 
{ 
    ... 
public: 
    ... 
    virtual ~State() = 0; 
    virtual void handleEvent(const EventPtr& pEvent) = 0; 
}; 

class StateA : public State 
{ 
    ... 
public: 
    void handleEvent(const EventPtr& pEvent) 
    { 
     switch(pEvent->id()) 
     { 
     case EVENT_1:   
      int n = boost::static_pointer_cast<Event1>(pEvent)->getN(); 
      ... 
      break; 
     case EVENT_2: 
      std::string s = boost::static_pointer_cast<Event2>(pEvent)->getS(); 
      ... 
      break; 
     ... 

     } 
    } 
} 

Trả lời

6

Các mẫu người truy cập điển hình thực hiện không yếu hèn, nhờ một chiến lược hai công văn:

// Visitor.hpp 
class EventBar; 
class EventFoo; 

class Visitor { 
public: 
    virtual void handle(EventBar const&) = 0; 
    virtual void handle(EventFoo const&) = 0; 
}; 

// Event.hpp 
class Visitor; 

class Event { 
public: 
    virtual void accept(Visitor&) const = 0; 
}; 

Và việc triển khai:

// EventBar.hpp 
#include <Event.hpp> 

class EventBar: public Event { 
public: 
    virtual void accept(Visitor& v); 
}; 

// EventBar.cpp 
#include <EventBar.hpp> 
#include <Visitor.hpp> 

void EventBar::accept(Visitor& v) { 
    v.handle(*this); 
} 

Điểm mấu chốt ở đây là ở v.handle(*this) loại tĩnh của *thisEventBar const&, chọn đúng quá tải virtual void handle(EventBar const&) = 0 quá tải trong Visitor.

+1

Downcast được thực hiện bằng cách gọi '' Event :: accept''. Nó được giải quyết qua vtable thành '' EventBar :: accept'' và '' this'' được truyền từ '' Event'' sang '' EventBar'' trong tiến trình. –

+0

Vì vậy, không có các mẫu/thành ngữ ma thuật nào khác để tránh downcasting? Mối quan tâm duy nhất của tôi với Khách truy cập là số lượng mã lặp lại cần phải được viết. Nhưng có vẻ như đó là giá của việc không bị mất tín hiệu. Tôi sẽ không có vấn đề này nếu tôi chỉ có một lớp Event duy nhất và không cần phải lưu trữ các lớp Event có nguồn gốc trong hàng đợi nhưng điều đó là không thể tránh khỏi. –

+0

@BojanKomazec: ít nhất một cách khác là sử dụng 'boost :: variant':' typedef boost :: biến thể Event; '. Bằng cách xóa cấu trúc phân cấp, bạn xóa các tệp bị hủy bỏ :) –

2

Ý tưởng về các sự kiện là truyền các đối tượng chi tiết qua giao diện tổng quát (và thuyết bất khả tri). Downcast là không thể tránh khỏi và một phần của thiết kế. Xấu hay tốt, nó có thể tranh chấp.

Mẫu khách truy cập chỉ ẩn quá trình truyền khỏi bạn. Nó vẫn được thực hiện phía sau hậu trường, các loại được giải quyết thông qua địa chỉ phương thức ảo.

Event của bạn đã có id, nó không hoàn toàn bất khả tri đối với loại, vì vậy việc truyền hoàn toàn an toàn. Ở đây bạn đang xem các loại cá nhân, trong mô hình khách truy cập bạn đang làm cho trình biên dịch chăm sóc đó.

"Bất cứ điều gì đi lên đều phải đi xuống".

+0

Tôi không đồng ý với việc sử dụng * an toàn *. Nó có thể hoạt động, nhưng nó rất dễ bị lỗi bởi vì nó là thủ công. Sử dụng các cơ chế ngôn ngữ đảm bảo rằng a) không có downcast sai lầm sẽ xảy ra và b) tất cả các loại "target" sẽ được xử lý đúng (chuyển đổi trên ID bạn có thể quên ID mới ...) –

+0

@MatthieuM. Bạn càng ít phải làm thủ công, càng ít chỗ cho lỗi, tôi đồng ý. Tuy nhiên năng lực đặt câu hỏi của một lập trình viên là một cách thẳng thắn để "Bạn là ngu ngốc để viết trong C + +" đối số. C và C++ dành cho những người biết họ đang làm gì. Đó là giả định của tôi. Đường cú pháp sai với chức năng là quá phổ biến. –

+0

* Bạn càng ít phải thực hiện thủ công, càng ít chỗ cho lỗi * - đây là tín dụng của tôi :) Tôi thích thiết kế như vậy. Và với khách truy cập không cần 'EventID'. –

Các vấn đề liên quan