Как это можно реализовать (элегантно) без использования RTTI? - PullRequest
1 голос
/ 12 октября 2010

Я кодировал это в C #, и самое быстрое решение, которое пришло на ум, - это использование ключевых слов "как" или "есть". Я начал задаваться вопросом, как я мог бы реализовать это аккуратно в C ++ (без RTTI) ... или даже в C # без вышеупомянутых ключевых слов.

Вот проблема (упрощенная):

Существует класс Command, который содержит поток так называемых "токенов".

class Command
{
    public List<Token> Toks {get; set;}

    //...
}

Жетон (в настоящее время) может быть «ключевым словом» или «игровым объектом».

class Token
{
    //nothing in here :(
}

class KWToken : Token
{
    public List<string> Synonyms {get; set;}
}

class GOToken : Token
{

}

Позже я хочу просмотреть список объектов Command из Token s и делать вещи, основываясь на типе Token.

Суть этого сценария в том, что KWToken объекты содержат List связанных строк, которые мне требуются.

Конечно, как уже говорилось, простое решение в C # будет использовать ключевое слово "is" или "as".

Я подумал о нескольких не слишком сексуальных способах сделать это.
У кого-нибудь есть красивые идеи?

РЕДАКТИРОВАТЬ

(Пример печати удален, поскольку он вводит в заблуждение)
Список синонимов в объекте KWToken будет доступен во многих местах через связанный объект Command. Не один раз, чтобы напечатать их, как я мог предположить из своего примера.

Ответы [ 7 ]

6 голосов
/ 12 октября 2010

Вы также можете попробовать использовать Шаблон посетителя .Хотя это, вероятно, излишне для вашего простого сценария.

4 голосов
/ 12 октября 2010

Это очень классический полиморфизм. Не будет ли следующая работа?

abstract class Token
{
    public abstract void DoStuff();
}

class KeywordToken : Token
{
    List<string> Synonyms { get; set; }
    public override void DoStuff()
    {
        foreach (string s in Synonyms)
        {
            Console.WriteLine(s);
        }
    }
}

class GameObjectToken : Token
{
    public override void DoStuff()
    {
        // Do something else.
    }
}


// Elsewhere
foreach (var token in cmd.Toks)
{
    token.DoStuff();
}
1 голос
/ 12 октября 2010

Как полагают другие, полиморфизм и паттерн Visitor кажутся естественными приспособлениями для этой проблемы.

В C ++ это также можно сделать обобщенно, используя такой класс, как boost::variant.

Ваш класс Command может быть определен примерно так:

class Command
{
public:
    std::vector<boost::variant<KWToken, GOToken> > Toks;

    //...
};

, и мы можем определить статический класс посетителя, определяющий операции, выполняемые с каждым типом объекта:

class my_visitor : public boost::static_visitor<void>
{
public:
    // define the operation on KWTokens
    void operator()(KWToken& tok)  const
    {
      //...
      // since tok is well typed, we can access its Synonyms member without any problems.
      DoStuff(tok.Synonyms)
    }
    // and the operation on GOTokens
    void operator()(GOToken& tok) const
    {
      //...
    }
};

Изатем, учитывая вариант v, содержащий один из типов токенов, вы можете вызвать посетителя следующим образом:

boost::apply_visitor( my_visitor(), v );

Подробнее см. http://www.boost.org/doc/libs/1_44_0/doc/html/variant.html.

Это позволяет избежать«традиционный» ООП-полиморфизм, предоставляющий вам полностью основанное на шаблонах решение (использующее статический полиморфизм).

В этом подходе вам даже не нужен общий базовый класс Token, а конкретные типы токеновне нужно реализовывать какие-либо виртуальные функции.

В C # у вас нет этой опции, и шаблон посетителя (который все еще является правильным решением) должен быть реализован с использованием динамическогополиморфизм.

1 голос
/ 12 октября 2010

Используйте функцию LINQ OfType () для извлечения только элементов в списке того типа, который вам нужен:

foreach(var kw in command.Toks.OfType<KWToken>())
{
  kw.Synonyms[...];
}
0 голосов
/ 13 октября 2010

Я не знаю достаточно о вашем сценарии, но это может быть возможность добавить методы

virtual bool hasSynonyms() = 0;
virtual List<std::string> getSynomyms() { return List<std::string>; /*empty list */ }

в базовый класс вашего токена.

Я бы сделал это только в том случае, если могут быть другие токены, которые могут иметь синонимы, в противном случае шаблон посетителя - это путь.

0 голосов
/ 12 октября 2010

Архитектура Microsoft COM обрабатывает это изящно.Базовый класс IUnknown содержит только минимально необходимую функциональность для получения интерфейса, а интерфейс определяет, что объект может или не может делать.В вашем случае вы могли бы определить интерфейс для списка ключевых слов, и если этот интерфейс возвращает NULL, то объект не поддерживает список ключевых слов.Вам не нужно реализовывать COM как таковой, я просто использую его в качестве примера.

Вы определяете объект в терминах того, что он делает, а не того, чем он является.

0 голосов
/ 12 октября 2010

Если вы не хотите использовать RTTI (если это возможно, я бы посоветовал вам его использовать), вы можете реализовать его самостоятельно.

#include <string>

template <class T>
class List
{};

class Token
{
    //nothing in here :(
public:
        enum TokenType
        {
                KWToken,
                GOToken
        };

        virtual TokenType Which() = 0;
};

class KWToken : public Token
{
public:
    List<std::string> Synonyms() {
        //get
        return List<std::string>();
    }
    void Synonyms(List<std::string>& value) {
        //set
    }
    virtual TokenType Which() {return Token::KWToken; }
};

class GOToken : public Token
{
        virtual TokenType Which() {return Token::GOToken; }

};

int main() {
  Token* T = GetToken();
  switch(T->What()) {
  case Token::KWToken:
     //Do fancy stuff
     static_cast<KWToken*>(T)->Synonyms();
     break;
  }
}
...