Как элегантно конвертировать switch + enum с полиморфизмом - PullRequest
2 голосов
/ 04 мая 2010

Я пытаюсь заменить простые перечисления на классы типов ... то есть один класс, производный от базы для каждого типа. Так, например, вместо:

enum E_BASE { EB_ALPHA, EB_BRAVO };
E_BASE message = someMessage();
switch (message) 
{
  case EB_ALPHA: applyAlpha();
  case EB_BRAVO: applyBravo();
}

Я хочу сделать это:

Base* message = someMessage();
message->apply(this); // use polymorphism to determine what function to call.

Я видел много способов сделать это, которые кажутся менее изящными, чем базовый оператор switch. Использование dyanimc_cast, унаследованного от класса messageHandler, который необходимо обновлять при каждом добавлении нового сообщения, с использованием контейнера указателей на функции, похоже, все они не позволяют облегчить поддержку кода путем замены переключателей полиморфизмом.

Это настолько близко, насколько я могу получить: (я использую шаблоны, чтобы избежать наследования от всезнающего интерфейса обработчика)

class Base
{
public:
    template<typename T> virtual void apply(T* sandbox) = 0;
};

class Alpha : public Base
{
public:
    template<typename T> virtual void apply(T* sandbox)
    {
        sandbox->applyAlpha();
    }
};

class Bravo : public Base
{
public:
    template<typename T> virtual void apply(T* sandbox)
    {
        sandbox->applyBravo();
    }
};

class Sandbox
{
public:
    void run()
    {
        Base* alpha = new Alpha;
        Base* bravo = new Bravo;

        alpha->apply(this);
        bravo->apply(this);

        delete alpha;
        delete bravo;
    }
    void applyAlpha() {
        // cout << "Applying alpha\n";
    }

    void applyBravo() {
        // cout << "Applying bravo\n";
    }
};

Очевидно, что это не компилируется, но я надеюсь, что это решит мою проблему.

Ответы [ 4 ]

3 голосов
/ 05 мая 2010

Что ж, после того, как я согласился на dynamic_cast и множественное наследование, я придумал это благодаря Энтони Уильямсу и jogear.net

class HandlerBase
{
public:
    virtual ~HandlerBase() {}
};

template<typename T> class Handler : public virtual HandlerBase
{
public:
    virtual void process(const T&)=0;
};

class MessageBase
{
public:
    virtual void dispatch(HandlerBase* handler) = 0;

    template<typename MessageType>
    void dynamicDispatch(HandlerBase* handler, MessageType* self)
    {
        dynamic_cast<Handler<MessageType>&>(*handler).process(*self);
    }
};

template<typename MessageType> class Message : public MessageBase
{
    virtual void dispatch(HandlerBase* handler)
    {
        dynamicDispatch(handler, static_cast<MessageType*>(this));
    }
};

class AlphaMessage : public Message<AlphaMessage>
{
};

class BravoMessage : public Message<BravoMessage>
{
};

class Sandbox : public Handler<AlphaMessage>, public Handler<BravoMessage>
{
public:
    void run()
    {
        MessageBase* alpha = new AlphaMessage;
        MessageBase* bravo = new BravoMessage;

        alpha->dispatch(this);
        bravo->dispatch(this);

        delete alpha;
        delete bravo;
    }
    virtual void process(const AlphaMessage&) {
        // cout << "Applying alpha\n";
    }

    virtual void process(const BravoMessage&) {
        // cout << "Applying bravo\n";
    }
};


int main()
{
    Sandbox().run();
    return 0;
}
2 голосов
/ 04 мая 2010

Ваши классы Bravo и Alpha фактически являются замыканиями ... Жаль, что C ++ не поддерживает их напрямую.

Вы можете использовать указатель члена, чтобы сделать это:

typedef void (Sandbox::*SandboxMethod)();

struct BrAlpha {
  BrAlpha(SandboxMethod method) : method(method){}
  void apply(Sandbox sb){sb->*method();}
};

BrAlpha alpha(&Sandbox::applyAlpha);
BrAlpha bravo(&Sandbox::applyBravo);

(синтаксис может быть не точным, но вы знаете, что я имею в виду)

2 голосов
/ 04 мая 2010

Похоже, вы пытаетесь найти какую-то систему двойной отправки. Посмотрите шаблон Visitor или другие системы множественной диспетчеризации.

0 голосов
/ 04 мая 2010

У меня не обязательно есть ответ на вашу проблему с шаблоном проектирования (хотя Современный C ++ Design может многое сказать об этом), но я хочу обратиться к вашему переключателю против комментария наследования.

Проблема с этим простым утверждением swtich - ремонтопригодность. Если этот оператор switch был в 1 месте, то, вероятно, примерно столько же набирается для создания классов и наследования, но этот оператор switch все еще является бомбой замедленного действия, ожидающей еще одного добавленного состояния без добавления случая для него. Если вы установите значение по умолчанию :, вы поймаете его во время выполнения - в конце концов, но это очень плохо. Если вы установили несколько указателей на функции и установили время компиляции в зависимости от размера таблицы, у вас все получилось, но это на другой уровень глубже, чем оператор switch. И все это исчезает, как только у вас появляется второе место в коде, которое должно проверить состояние.

Намного проще, когда вы настроите класс интерфейса, чтобы компилятор мог обрабатывать весь нежелательный код включения состояний внутри. При добавлении класса не нужно беспокоиться о каком-либо другом коде, если вы следуете интерфейсу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...