2012-04-11 53 views
2

我正在編寫一個工具,通過更改設置,然後流式傳輸信息,使用戶能夠與一些硬件進行交互。如何在通過隊列傳遞信息時避免向下轉換?

要做到這一點,我有幾個線程運行:EquipmentInterfaceDataProcessor它們通過Queue連接。

EquipmentInterface螺紋具有的方法來改變所述設備(RotateRefocus例如),並將所得的信息(CurrentAngleCurrentFocalDistance)被添加到Queue上設置。一旦設置正確,有方法StartStreamingStopStreaming,並且一旦流式傳輸開始,將來自設備的數據打包並添加到隊列中。

放置在隊列中的所有信息都來自單個BaseMessage類,其中包含消息類型的指示。然後,我得到了角度,焦距,開始和結束流式以及數據本身的消息類型。

DataProcessor監聽隊列的另一端,並根據當前角度/焦距,處理後續數據。

現在,事情是,我在數據處理器中有一個函數,它使用switch語句對進入的消息進行類型檢查。然後這些消息被下載到合適的類型並傳遞給適當的處理程序。實際上,除了一個DataProcessor監聽單個隊列外,實際上還有多個隊列中的多個監聽器(一些存儲到磁盤,一些顯示Gui上的信息)。每次添加一些信息時,我必須創建一個新的BaseMessage派生類,向該基類添加一個新類型,然後更新每個使用者中的switch語句以應對新消息。

關於這個架構的一些事情對我來說感覺不對,而且我最近一直在閱讀很多關於下載的內容。從我所看到的情況來看,普遍的共識似乎是what I'm doing is a bad code smell。我見過a suggestion which use Boost,但他們看起來沒有比我更清楚的switch語句(也許我錯過了什麼?)。

所以我的問題是:我應該試圖避免switch-statement/downcasting解決方案,如果是這樣,如何?

我的實現是在C++/CLI中,因此.net或C++解決方案就是我所追求的。

編輯研究 - 基於iammilind和stfaanv的意見,這是諸如此類的事情,你是在暗示:

class QueuedItem 
{ 
public: 
    QueuedItem() { } 
    virtual ~QueuedItem() { } 

}; 

class Angle : public QueuedItem 
{ 
public: 
    Angle() {} 
    virtual ~Angle() { } 
}; 

class FocalLength : public QueuedItem 
{ 
public: 
    FocalLength() {} 
    virtual ~FocalLength() { } 
private: 

}; 


class EquipmentHandler 
{ 
protected: 
    virtual void ProcessAngle(Angle* angle) {}; 
    virtual void ProcessFocalLength(FocalLength* focalLength) {}; 

public: 
    void ProcessMessages(QueuedItem* item) 
    { 
     Angle* pAngle = dynamic_cast<Angle*>(item); 
     if(pAngle != NULL) 
     { 
      ProcessAngle(pAngle); 
     } 
     FocalLength* pFocalLength = dynamic_cast<FocalLength*>(item); 
     if(pFocalLength != NULL) 
     { 
      ProcessFocalLength(pFocalLength); 
     } 

    } 
}; 

class MyDataProcessor : public EquipmentHandler 
{ 
protected: 
    virtual void ProcessAngle(Angle* angle) override { printf("Processing Angle"); } 
    virtual void ProcessFocalLength(FocalLength* focalLength) override { printf("Processing FocalLength"); }; 
}; 


int _tmain(int argc, _TCHAR* argv[]) 
{ 

    // Equipment interface thread... 
    FocalLength* f = new FocalLength(); 
    QueuedItem* item = f; // This gets stuck onto the queue 

    // ...DataProcessor thread (after dequeuing) 
    QueuedItem* dequeuedItem = item; 

    // Example of a DataProcessor implementation. 
    // In reality, this would 
    MyDataProcessor dataProc; 
    dataProc.ProcessMessages(dequeuedItem); 

    return 0; 
} 

...並可以將其簡化? ProcessMessages感覺有點笨重,但這是我能夠看到的,在基類中沒有switch語句和某種枚舉消息類型標識符的唯一方法。

+1

看起來你正迫切需要* Dynamic Polymorphism和Dynamic dispatch *。 – 2012-04-11 07:12:05

+0

爲什麼不在聲明基類中的'virtual'函數並在其所有子項中實現? – iammilind 2012-04-11 07:13:12

+0

@iammilind:這肯定會節省多次重新實現switch語句,但並沒有完全擺脫它。這是確定要調用哪個處理程序的最有效方法嗎? – 2012-04-11 07:24:06

回答

2

你可以嘗試訪問者設計模式:http://en.wikipedia.org/wiki/Visitor_pattern

每個數據處理器將從一個BaseVisitor類,它定義用於處理每一種類型的消息的虛擬方法繼承。基本上這些方法只是noop。

當您定義新的消息類型時,您在BaseVisitor中爲此消息類型添加了一個新的虛擬方法,其中noop實現。然後,如果子類DataProcessor類想要處理此消息類型,則僅覆蓋此DataProcessor中的虛擬方法。所有其他DataProcessor保持不變。

#include <iostream> 


    class FocalLength; 
    class Angle; 
    class EquipmentVisitor; 

    class QueuedItem 
    { 
    public: 
      QueuedItem() { } 
      virtual ~QueuedItem() { } 

      virtual void AcceptVisitor(EquipmentVisitor& visitor) = 0; 
    }; 

    class EquipmentVisitor 
    { 
    public: 
      virtual ~EquipmentVisitor() {} 

      virtual void Visit(FocalLength& item) {} 
      virtual void Visit(Angle& item)  {} 

      void ProcessMessages(QueuedItem* item) 
      { 
        item->AcceptVisitor(*this); 
      } 
    }; 

    class Angle : public QueuedItem 
    { 
    public: 
      Angle() {} 
      virtual ~Angle() { } 

      void AcceptVisitor(EquipmentVisitor& visitor) { visitor.Visit(*this); } 
    }; 

    class FocalLength : public QueuedItem 
    { 
    public: 
      FocalLength() {} 
      virtual ~FocalLength() { } 

      void AcceptVisitor(EquipmentVisitor& visitor) { visitor.Visit(*this); } 
    private: 

    }; 

    class MyDataProcessor : public EquipmentVisitor 
    { 
    public: 
      virtual ~MyDataProcessor() {} 

      void Visit(Angle& angle)    { std::cout << "Processing Angle" << std::endl; } 
      void Visit(FocalLength& focalLength) { std::cout << "Processing FocalLength" << std::endl; } 
    }; 


    int main(int argc, char const* argv[]) 
    { 
      // Equipment interface thread... 
      FocalLength* f = new FocalLength(); 
      QueuedItem* item = f; // This gets stuck onto the queue 

      // ...DataProcessor thread (after dequeuing) 
      QueuedItem* dequeuedItem = item; 

      // Example of a DataProcessor implementation. 
      // In reality, this would 
      MyDataProcessor dataProc; 
      dataProc.ProcessMessages(dequeuedItem); 

      return 0; 
    } 
+0

就像我添加到問題中的示例一樣? – 2012-04-11 21:51:13

+0

太棒了!這是一個非常整潔的解決方案,現在我已經真正瞭解它的工作原理了:-) – 2012-04-12 15:03:09

0

你可以做以下任一操作:

委託的處理代碼(如在每個caseswitch語句),以Handler對象 - 無論是HandlerBase對象,或完全無關的類型的層次結構。

然後你有你的消息保持到Handler對象的引用(如果它是一個層次,你可以在BaseMessage水平做到這一點,如果不相關的對象,然後根據各個專業的消息類型的一部分),您可以向其中再通過BaseMessage::Handle()方法處理它們時傳遞它們。編輯:這種方法不是虛擬的。

當然,如果你走HandlerBase層次的路徑,你仍然需要static_cast消息回他們是什麼類型的,但應該是罰款:他們只能用自己的處理程序來創建(這是無論如何,應該知道他們的類型)。

實施例:

// BaseMessage.hpp 
#include <iostream> 

class BaseMessage 
{ 
public: 
    BaseMessage(HandlerBase* pHandler); 
    : m_pHandler(pHandler) 
    {} 

    virtual ~BaseMessage() 
    {} 

    void SetHandler(HandlerBase* pHandler) 
    { 
    m_pHandler = pHandler; 
    } 

    void Handle() 
    { 
    assert(m_pHandler != 0); 
    m_pHandler->Handle(this); 
    } 

protected: 
    HandlerBase* m_pHandler; // does not own it - can be shared between messages 
}; 

// HandlerBase.hpp 
class HandlerBase 
{ 
public: 
    HandlerBase() 
    {} 

    virtual ~HandlerBase() 
    {} 

    virtual void Handler(BaseMessage* pMessage) =0; 
} 

// message and handler implementations 
class AMessage: public BaseMessage 
{ 
public: 
    AMessage(BaseHandler* pHandler) 
    : BaseMessage(pHandler) 
    {} 

    ~AMessage() {} 

    void DoSomeAness() 
    { 
    std::cout << "Being an A..." << std::endl; 
    } 
}; 

class AHandler 
{ 
public: 
    AHandler() 
    {} 

    virtual ~AHandler() 
    {} 

    virtual void Handle(BaseMessage* pMessage) 
    { 
    AMessage *pMsgA(static_cast<AMessage*>(pMessage)); 
    pMsgA->DoSomeAness(); 
    } 
}; 

class BMessage: public BaseMessage 
{ 
public: 
    BMessage(BaseHandler* pHandler) 
    : BaseMessage(pHandler) 
    {} 

    ~BMessage() {} 

    void DoSomeBness() 
    { 
    std::cout << "Being a B..." << std::endl; 
    } 
}; 

class BHandler 
{ 
public: 
    BHandler() 
    {} 

    virtual ~BHandler() 
    {} 

    virtual void Handle(BaseMessage* pMessage) 
    { 
    BMessage *pMsgB(static_cast<BMessage*>(pMessage)); 
    pMsgB->DoSomeBness(); 
    } 
}; 


// the thread 
static std::list<BaseMessage*> msgQueue; 

int HandlerThread(void *pData) 
{ 
    while(true) // find some more sophisticated way to break 
    { 
    while(!msgQueue.empty()) 
    { 
     msgQueue.front()->Handle(); 
     msgQueue.pop_front(); 
    } 
    // delay and stuff 
    } 
    return 0; 
} 

int main(int argc, char** argv) 
{ 
    start_thread(HandlerThread, 0); // your favorite API here 

    AHandler aHandler; 
    BHandler bHandler; 

    msqQueue.push_back(new AMessage(&aHandler)); 
    msqQueue.push_back(new BMessage(&bHandler)); 
    msqQueue.push_back(new BMessage(&bHandler)); 
    msqQueue.push_back(new AMessage(&aHandler)); 
    msqQueue.push_back(new AMessage(&aHandler)); 
    msqQueue.push_back(new BMessage(&bHandler)); 
    msqQueue.push_back(new AMessage(&aHandler)); 
    msqQueue.push_back(new BMessage(&bHandler)); 

    return 0; 
} 

編輯:是的,在本質上,這是visitor pattern

+0

這是否意味着每個對象都需要知道(何時創建)什麼會處理它?你能提供一個具體的例子嗎? – 2012-04-11 07:28:31

+0

添加了一些示例代碼。對象並不一定要知道他們的處理程序,但是在SetHandler()被創建並添加到隊列後,這會很痛苦,主要是因爲隊列的工作方式以及它們看起來像是BaseMessage *秒。您可以添加一些其他設施,瞭解您的消息類型,映射到任何信息,並可以分別設置它們的處理程序。 – zyndor 2012-04-11 09:21:20

+0

@ iCE-9:你的解決方案似乎是消息分派和雙派遣之間的一半,這兩者都更加優雅並避免了投射。如果你的消息是特定於處理程序的,只需使用消息中特定的處理程序,並通過虛擬handle()函數對處理程序進行正確的調用。如果消息可能用於任何處理程序,但結果不同,請通過消息中的虛擬句柄()(或接受)函數和被稱爲處理函數的重載(每個消息)act()(或訪問)函數進行雙重調度通過msg :: handle()函數。 – stefaanv 2012-04-11 09:26:22

0

處理根據我用於發送4個信息給2個處理程序最簡單的消息:

#include <iostream> 
#include <queue> 
#include <memory> 

class HandlerA 
{ 
public: 
    void doA1() { std::cout << "A1\n"; } 
    void doA2(const std::string& s) { std::cout << "A2: " << s << "\n"; } 
}; 

class HandlerB 
{ 
public: 
    void doB1() { std::cout << "B1\n"; } 
    void doB2(const std::string& s) { std::cout << "B2: " << s << "\n"; } 
}; 


class BaseMsg 
{ 
public: 
    virtual ~BaseMsg() {} 
    void send(); 
    virtual void handle() { execute(); } 
    virtual void execute() = 0; 
}; 

typedef std::shared_ptr<BaseMsg> Msg; 
class Medium 
{ 
    std::queue<Msg> queue; 
public: 
    void send(Msg msg) { queue.push(msg); } 
    void process() 
    { 
    while (! queue.empty()) 
    { 
     std::cout << "Processing\n"; 
     queue.front()->handle(); 
     queue.pop(); 
    } 
    } 
}; 

class BaseMsgHndlrA : public BaseMsg 
{ 
protected: 
    HandlerA& ha; 
public: 
    BaseMsgHndlrA(HandlerA& ha_) : ha(ha_) { } 
}; 

class BaseMsgHndlrB : public BaseMsg 
{ 
protected: 
    HandlerB& hb; 
public: 
    BaseMsgHndlrB(HandlerB& hb_) : hb(hb_) { } 
}; 

class MsgA1 : public BaseMsgHndlrA 
{ 
public: 
    MsgA1(HandlerA& ha_) : BaseMsgHndlrA(ha_) { } 
    virtual void execute() { ha.doA1(); } 
}; 

class MsgA2 : public BaseMsgHndlrA 
{ 
public: 
    MsgA2(HandlerA& ha_) : BaseMsgHndlrA(ha_) { } 
    virtual void execute() { ha.doA2("Msg A2"); } 
}; 

class MsgB1 : public BaseMsgHndlrB 
{ 
public: 
    MsgB1(HandlerB& hb_) : BaseMsgHndlrB(hb_) { } 
    virtual void execute() { hb.doB1(); } 
}; 

class MsgB2 : public BaseMsgHndlrB 
{ 
    std::string s; 
public: 
    MsgB2(HandlerB& hb_, const std::string s_) : BaseMsgHndlrB(hb_), s(s_) { } 
    virtual void execute() { hb.doB2(s); } 
}; 

int main() 
{ 
    Medium medium; 
    HandlerA handlerA; 
    HandlerB handlerB; 

    medium.send(Msg(new MsgA1(handlerA))); 
    medium.send(Msg(new MsgA2(handlerA))); 
    medium.send(Msg(new MsgB1(handlerB))); 
    medium.send(Msg(new MsgB2(handlerB, "From main"))); 

    medium.process(); 
} 

這僅使用虛擬功能分派到與某些參數的權利處理程序。
handle()函數並非嚴格需要,但在定義消息層次結構時很有用。
一個通用的消息可以包含一個std :: function,它可以用bind來填充,所以可以發送帶有參數的實際函數,而不是每個排隊的動作都創建一個消息類。
要隱藏實際的發送,處理程序可以自己發送,因此可以從發送線程立即訪問它們。

如果有多條消息需要發送給更多的處理程序,則可以使用雙重發送(訪問者)。

+0

正如我在問題中所說的,完整的應用程序涉及多個消息(全部來自單個公共基礎)被髮送到各種的處理程序。 – 2012-04-12 15:38:36

+0

這回答你的問題(我顯示了兩個處理程序訪問的消息)。然而,正如我已經評論過的那樣,您可以在這個單一的調度解決方案和fjardon的double dipatch解決方案之間做出選擇,甚至可以選擇鑄造解決方案,所以這是您的呼叫,顯然是雙重調度。 – stefaanv 2012-04-13 06:35:07