4

This will be in C++11, but the object design should be rather agnostic.

I have 3 interface classes, IEvent, ICondition, IRunnable.

The main loop processes multiple kinds of IEvent, ranging from "an Application has launched" to "User clicked position (x,y)". Then for each type of event, a list of IConditionare notified through a listener interface. For example, ConditionUserClickedPos implements IEventClickListener::onClick(int x, int y), which is called by the main loop.

If the position is within the conditions, one or more IRunnable objects are called through IRunnable::execute(IEvent triggeringEvent), like class RunnableDrawCircleAtClickPosition and class RunnableShowPrettyWindow.

My problem is as follow :

Within IRunnable::execute(IEvent triggeringEvent) implementation, I need to get the precise information of the event, but I don't know the exact class hierarchy of the triggeringEvent.

This can be easily solved with dynamic_cast, checking typeid or other reflective oop methods, but those usually breed rather smelly code. I'm also trying to keep the code extendable for new and unrelated events, conditions and runnables that I don't know about yet.

I could also declare a bunch of overloads for ::execute(...) based on children class types, but this doesn't seem right either.

Is there a silver bullet that I've missed here?

4
  • 1
    I'm no expert on these systems, but I always liked SFML's approach of using a simple union without any classes or inheritance. An if(event.type === ...) is much more palatable than a std::dynamic_cast, at least for me.
    – Ixrec
    Jun 1, 2015 at 23:20
  • I don't think there is enough information here to write up a complete answer, but I will ask if you are familiar with inversion of control. It is a powerful tool for abstracting away these type checks.
    – user22815
    Jun 1, 2015 at 23:26
  • Thanks for the suggestion Snowman. Inversion of control is precisely what I'm trying to achieve here. At first I thought I would be ok by passing the triggering event down to the IRunnable and letting it be handled in the runnable's implementation. I just don't know how to handle it down the chain. I'll look at the wikipedia article more closely for additional clues.
    – Eric
    Jun 1, 2015 at 23:58
  • @Ixrec : "===", javascript background huh? :p. I could use enumerations to "tag" my class types and check these at runtime. It would save some of the problems caused by dynamic casting, but it still doesn't "feel" like the "right" solution. Also, the SFML approach works by coupling the event dispatch loop with "action" code, like window manipulation and such. The 3rd layer seems to be what creates all the problems.
    – Eric
    Jun 2, 2015 at 0:03

1 Answer 1

1

My suggestion is below written in PHP pseudocode. So, we have many types of events, all implementing IAbstractEvent interface. When we need to process an event, we give this event to a factory method FactoryConstructor::constructFactory(). This method creates abstract factory AbstractLoopProcessorFactory descendant for us for processing events of this particular type.

When we have an event which name is DrawCircle, factory method creates DrawCircleLoopProcessorFactory for us. This factory can create concrete DrawCirlceConditionProcessor and DrawCirlceRunnable that can work with IDrawCircleEvent event.

IDrawCircleEvent interface may have an arbitrary number of methods and arbitrary structure. Only it should extend IAbstractEvent to allow our abstract factory to work. So, in DrawCirlceConditionProcessor you already know, that actual event passed to you is IDrawCircleEvent and you can reference it as is.

A type cast is probably required because you have to be able to work with an event queue uniformly. But you trade it for logic separation.

interface IAbstractEvent
{
    public function getName();
}

interface IDrawCircleEvent extends IAbstractEvent
{

}

interface IAbstractConditionProcessor
{
    /**
     * @return bool
     */
    public function check(IAbstractEvent $event);
}

interface IDrawCircleConditionProcessor extends IAbstractConditionProcessor
{

}

interface IAbstractRunnable
{
    /**
     * @param IAbstractEvent $event
     *
     * @return void
     */
    public function process(IAbstractEvent $event);
}

interface IDrawCircleRunnable extends IAbstractRunnable
{

}

abstract class AbstractLoopProcessorFactory
{
    /**
     * @param IAbstractEvent $event
     *
     * @return IAbstractConditionProcessor
     */
    abstract function getConditionProcessor(IAbstractEvent $event);

    /**
     * @param IAbstractEvent $event
     *
     * @return IAbstractRunnable
     */
    abstract function getRunnable(IAbstractEvent $event);
}

class DrawCircleLoopProcessorFactory extends AbstractLoopProcessorFactory
{
    public function getConditionProcessor(IAbstractEvent $event)
    {
        return new DrawCirlceConditionProcessor($event);
    }

    public function getRunnable(IAbstractEvent $event)
    {
        return new DrawCirlceRunnable($event);
    }
}

class FactoryConstructor
{
    /**
     * @param IAbstractEvent $event
     *
     * @return AbstractLoopProcessorFactory
     * @throws Exception
     */
    public static function constructFactory(IAbstractEvent $event)
    {
        switch ($event->getName()) {
            case "DrawCircle":
                return new DrawCircleLoopProcessorFactory();
            default:
                throw new Exception("Unknown event name {$event->getName()}");
        }
    }
}

class Application
{
    public static function main()
    {
        /**
         * @var IAbstractEvent[]
         */
        $eventList = [];

        foreach ($eventList as $event) {
            $factory = FactoryConstructor::constructFactory($event);
            $conditionChecker = $factory->getConditionProcessor();
            if ($conditionChecker->check($event)) {
                $runnable = $factory->getRunnable($event);
                $runnable->process($event);
            }
        }

    }
}
6
  • So the idea is to centralize the reflection in an abstract factory, then the concrete factories will know enough about the types they handle to avoid further manual casting. This is certainly better than having each event handle their own dynamic casting. I'll see how this can work for me.
    – Eric
    Jun 2, 2015 at 22:23
  • @Eric Yep. After you make decision, tell me the results if possible, please. Jun 3, 2015 at 7:38
  • I ran into some problems with this approach. In my design, the conditions depends on the events and not the other way around. The events should have no knowledge of what may happen when they are raised. Maybe my formulation of the problem wasn't clear enough, or I don't understand your implementation correctly. The constructFactory() call implies that the event know about "DrawCircle" before conditions are checked, while I was trying to have the "DrawCircle" decision taken based on the conditions.
    – Eric
    Jun 4, 2015 at 3:52
  • Also I am not sure that it truly solve my problem. In process(), how can I get the event (x,y) position from IAbstractEvent? How can a different runnable get "Application name which just launched" from IAbstractEvent as well? This will most probably involve some dynamic casting and runtime type checking.
    – Eric
    Jun 4, 2015 at 3:55
  • @Eric I will post some clarifications to my approach tonight. Jun 4, 2015 at 7:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.