我有一个状态模式的实现,其中每个状态处理从事件队列获取的事件。因此,基础State
类具有纯虚方法void handleEvent(const Event*)
。事件继承基类Event
类,但每个事件都包含可以是不同类型的数据(例如int,string ......或其他)。 handleEvent
必须确定接收事件的运行时类型,然后执行 downcast 以提取事件数据。事件是动态创建的并存储在队列中(所以 upcasting 发生在这里......)。
我知道向下倾斜是设计糟糕的标志,但在这种情况下是否有可能避免它?我在考虑访客模式,其中基类状态会包含每个事件的虚拟处理程序,但是再次向下转换将需要在代码段中进行,该代码段将事件从队列中取消并将其传递给当前状态。 (至少在这种情况下,大switch(eventID)
只会在一个地方......)。 访客模式是避免向下转发的最佳方式(最佳做法)吗?
这是伪代码(我在这个例子中传递boost::shared_ptr
,但无论如何都会发生向下转换):
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;
...
}
}
}
答案 0 :(得分:7)
由于双重调度策略,典型的访客模式不会执行任何向下投射:
// 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;
};
实施:
// 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);
}
此处的关键点是v.handle(*this)
*this
的静态类型为EventBar const&
,它会在virtual void handle(EventBar const&) = 0
中选择正确的Visitor
重载。
答案 1 :(得分:2)
事件的想法是通过通用(和不可知)接口传递详细对象。 垂头丧气是不可避免的,也是设计的一部分。不好或好,这是有争议的。
访客模式只会隐藏您的投射。它仍然在幕后执行,类型通过虚拟方法地址解析。
因为你的Event
已经拥有了id,所以它并不是完全不可知的类型,所以强制转换非常安全。在这里,您正在亲眼观看类型,在访问者模式中,您正在编译器处理它。
“无论什么事情都要发展下去”。