Question
Imagine I have some simulator library, that takes from me some objects (aka event handlers) and generates events for these objects by calling their handle_event(Event)
method. The library provides me with the following classes:
class Event {}; // Base class for all events
// All event classes are derived from `Event`
class SomeParticularEvent : public Event {}; // Some event class
class AnotherParticularEvent : public Event {}; // Some event class
// Object aka event handler. I should inherit this class and then give objects to the simulator
class AbstractEventHandler
{
public:
virtual void handle_event(Event) = 0;
};
I want to implement an object that handles different events differently. The first code I came up with is the following:
#include <iostream>
class MyObject : public AbstractEventHandler
{
public:
void actual_handle_event(SomeParticularEvent)
{
std::cout << "`SomeParticularEvent` occurred\n";
}
void actual_handle_event(AnotherParticularEvent)
{
std::cout << "`AnotherParticularEvent` occurred\n";
}
void actual_handle_event(Event e)
{
std::cerr << "Unknown event type occurred\n";
}
virtual void handle_event(Event e) override
{
actual_handle_event(e);
}
};
Сontrary to my expectations, MyObject::handle_event(Event)
will always call MyObject::actual_handle_event(Event)
regardless of the dynamic type of e
.
My question is: What is the correct way to implement MyObject
(preferrably, making it possible to easily add new event type)?
All the code together
#include <iostream>
class Event {}; // Base class for all events
// All event classes are derived from `Event`
class SomeParticularEvent : public Event {}; // Some event class
class AnotherParticularEvent : public Event {}; // Some event class
class AbstractEventHandler
{
public:
virtual void handle_event(Event) = 0;
};
class MyObject : public AbstractEventHandler
{
public:
void actual_handle_event(SomeParticularEvent)
{
std::cout << "`SomeParticularEvent` occurred\n";
}
void actual_handle_event(AnotherParticularEvent)
{
std::cout << "`AnotherParticularEvent` occurred\n";
}
void actual_handle_event(Event e)
{
std::cerr << "Unknown event type occurred\n";
}
virtual void handle_event(Event e) override
{
actual_handle_event(e);
}
};
int main()
{
MyObject o{};
o.handle_event(SomeParticularEvent{}); // Prints "Unknown event type occurred"
}
Additional question
Is it also possible to create a class derived from MyObject
and implement additional event handlers (not override old ones, but add support for new events) without rewriting the handle_event
method in the derived class?
CodePudding user response:
Based on the requirements in the question and things discussed in the comments to @ypnos answer, I believe that you want or need to implement the visitor pattern here.
One possible implementation would look like this (based on the Wikipedia article about Visitor Pattern: https://en.wikipedia.org/wiki/Visitor_pattern#C _example)
#include <iostream>
// Forward declare all Event classes
class Event;
class SomeParticularEvent;
class AnotherParticularEvent;
class AbstractEventHandler
{
public:
virtual void handle_event(const Event&) = 0;
virtual void handle_event(const SomeParticularEvent&) = 0;
virtual void handle_event(const AnotherParticularEvent&) = 0;
};
class Event {
public:
// virtual function for accepting the "visitor"
virtual void accept(AbstractEventHandler& handler) {
handler.handle_event(*this);
}
}; // Base class for all events
// All event classes are derived from `Event`
class SomeParticularEvent : public Event {
public:
// Needs to be overriden to call `handle_event` with the correct type
void accept(AbstractEventHandler& handler) override {
handler.handle_event(*this);
}
}; // Some event class
class AnotherParticularEvent : public Event {
public:
void accept(AbstractEventHandler& handler) override {
handler.handle_event(*this);
}
}; // Some event class
class MyObject : public AbstractEventHandler
{
public:
void handle_event(const SomeParticularEvent&) override
{
std::cout << "`SomeParticularEvent` occurred\n";
}
void handle_event(const AnotherParticularEvent&) override
{
std::cout << "`AnotherParticularEvent` occurred\n";
}
void handle_event(const Event& e) override
{
std::cerr << "Unknown event type occurred\n";
}
};
int main()
{
MyObject o{};
SomeParticularEvent{}.accept(o);
AnotherParticularEvent{}.accept(o);
}
CodePudding user response:
In your method handle_event()
object slicing occurs. You should pass (const) references for the inheritance to work correctly, or pointers.
However, this does not help with your case when dispatching internally from handle_event()
to actual_handle_event()
.
A brute-force solution would be to explicitely dispatch by trying dynamic_casts
. You would need to override handle_event()
in derived classes to extend it. It also doesn't look that pretty (indicating a design problem).
What is typically done in these situations is that the objects in question (Event
) would provide an interface for querying the type, e.g. an abstract type()
getter that is overridden.