我正在编写一个工具,使用户能够通过更改设置然后流式传输信息来与一些硬件进行交互。
为此,我正在运行几个线程:EquipmentInterface
和DataProcessor
,它们通过Queue
连接。
EquipmentInterface
线程具有更改设备设置的方法(例如Rotate
和Refocus
),结果信息(CurrentAngle
和CurrentFocalDistance
)被添加到Queue
中。设置正确后,有StartStreaming
和StopStreaming
的方法,一旦流式传输开始,来自设备的数据就会被打包并添加到队列中。
放置在队列上的所有信息都来自单个BaseMessage
类,其中包括消息类型的指示。然后,我派生了角度、焦距、开始和结束流的消息类型,当然还有数据本身。
DataProcessor
侦听队列的另一端,并根据当前角度/焦距处理后续数据。
现在,问题是,我在数据处理器中有一个函数,它使用 switch 语句来类型检查传入的消息。然后将这些消息向下转换为适当的类型,并传递给适当的处理程序。实际上,不仅仅是一个 DataProcessor 侦听单个队列,而是多个队列上的多个侦听器(一些存储到磁盘,一些在 gui 上显示信息)。每次添加一些信息时,我都必须创建一个新的BaseMessage
派生类,向该基类添加一个新类型,然后更新每个使用者中的 switch 语句以处理新消息。
关于这种架构的某些东西对我来说感觉不对劲,我最近读了很多关于向下投射的信息。从我所看到的情况来看,普遍的共识似乎是我正在做的是一种糟糕的代码气味。我看到了一个使用 Boost 的建议,但它们对我来说看起来并不比 switch 语句更干净(也许我错过了一些东西?
所以我的问题是:我是否应该尝试避免开关语句/向下转换解决方案,如果是这样,如何?
我的实现是在 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 语句和基类中的某种枚举消息类型标识符。
> 您可以尝试访客设计模式: http://en.wikipedia.org/wiki/Visitor_pattern
每个 DataProcessor 都将继承自一个 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;
}
您可以执行以下任一操作:
将处理代码(如 switch
语句中的每个case
)委托给Handler
对象 - 可以是HandlerBase
对象的层次结构,也可以是完全不相关的类型。
然后,您让消息保留对Handler
对象的引用(如果它是一个层次结构,则可以在BaseMessage
级别执行此操作,如果不相关的对象,则作为各个专用消息类型的一部分),然后您可以在处理它们时通过BaseMessage::Handle()
方法将它们传递给该对象。编辑:此方法不是虚拟的。
当然,如果你沿着HandlerBase
层次结构的道路走下去,你仍然需要将消息static_cast
回它们是什么类型,但这应该没问题:无论如何,它们应该只使用自己的 Handler(应该知道它们的类型)创建。
例:
// 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;
}
编辑:是的,本质上,这是访问者模式。
根据我的说法,最简单的消息处理,用于向 4 个处理程序发送 2 条消息:
#include <iostream>
#include <queue>
#include <memory>
class HandlerA
{
public:
void doA1() { std::cout << "A1n"; }
void doA2(const std::string& s) { std::cout << "A2: " << s << "n"; }
};
class HandlerB
{
public:
void doB1() { std::cout << "B1n"; }
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 << "Processingn";
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::函数,因此可以发送带有参数的实际函数,而不是为每个排队的操作创建一个消息类。
为了隐藏实际的发送,处理程序可以自己执行发送,因此可以从发送线程立即访问它们。
但是,如果需要将多个消息发送到更多处理程序,则可以使用双重调度(访问者)。