Обновить
59
0

Пользователь

Отправить сообщение
Я думаю, что это из-за того, что в проект включены все исходники библиотеки Box2D и статически прилинкована стандартная библиотека.
Основа — это чистый С++, без Qt. Я использовал Qt для того, чтобы игра работала на десктопе и телефонах Nokia. Objective-C можно использовать для того, чтобы портировать игру на iOS. Причем весь существующий С++-код не нужно будет переписывать на Objective-C, в этом большой плюс.
Я с вами согласен, но подумайте вот о чем: Angry birds следуют этим гайдам? Это им сильно мешает?
В играх интерфейс-гайды не столь важны, это больше касается других программ.
Версии под все заявленные платформы можете собрать сами, вот исходники. Игра имеет размер 320х480, разве это много? Возможно частота перерисовки HTML5 Canvas примерно в 60 fps на вашем оборудовании создает дрожание, я у себя не замечал.
К сожалению, пока только Google Chrome имеет такую прогрессивную технологию, как Native Client.
При использовании самого популярного и удобного решения, Necessitas — не работает. Вот issue, будем надеяться, что в недалеком будущем пофиксят. Жаль, что у главного разработчика нет мотивации реализовать это самому, возможно найдется кто-то другой.
Я могу продолжить ваш список. Мало кто из начинающих Java программистов знает, что такое observer, зато все прекрасно понимают, что такое listener. Я считаю, не нужно расстраиваться по этому поводу, а стараться уметь оперировать различными терминами, маркетинговыми и не очень.
Ведь buzzwords в паттернах играют огромную роль, поскольку позволяют быстро объяснить или узнать, как взаимодействуют компоненты в системе. Одна из основных задач паттерна — описать общеиспользуемый подход одним своим названием.
Самый большой недостаток вашей идеи в том, что метод accept в классе Orange невиртуальный. Соответственно вы не сможете хранить посещаемые объекты по указателям на базовый класс. Если же вы не храните объекты по указателям на базовый класс, то исчезает необходимость использовать visitor, можно обойтись простой перегрузкой методов:
class Apple {};
class Orange {};

class Drawer
{
public:
    void draw(Apple& apple);
    void draw(Orange& orange);
};

int main()
{
    Drawer drawer;
    Apple apple;
    Orange orange;
    drawer.draw(apple);
    drawer.draw(orange);
    return 0;
}
Поздравляю вас, если вы понимаете, что visitor и double dispatch это связанные вещи, то вы понимаете, как работает паттерн, хотя visitor — это не double dipatch. Правильнее говорить: «использует double dispatch» или «основан на double dispatch». Из википедии: «The visitor takes the instance reference as input, and implements the goal through double dispatch.»
Double dispatch сам по себе не подразумевает наличие иерархии, как посещаемых объектов, так и visitor'ов.
Простой пример double dispatch'а:
class Human
{
public:
    void eat(Apple& apple) { cout << "Yum-yum"; }
    void eat(Orange& orange) { cout << "Yum-yum-yuuuum!"; }
};

class Apple
{
public:
    void feed(Human& human) { human.eat(*this); }
};

class Orange
{
public:
    void feed(Human& human) { human.eat(*this); }
};

int main()
{
    Human human;
    Apple apple;
    apple.feed(human);
    Orange orange;
    orange.feed(human);
    return 0;
}

Как видим, тут нет базового класса visitor'a и базового посещаемого класса, а также ключевого слова accept. Вместо этого голый double dispatch :)
Вы пишете, что реализацию методов accept() можно вынести в отдельный файл visitor'a, а в вашем коде реализация в файле orange.cpp, не совсем понятно.
Если конечное действие происходит в посещаемом классе, то visitor не нужен, сойдет обычный виртуальный метод:
class Object
{
public:
    virtual void draw() = 0;
};

class Orange : public Object
{
public:
    virtual void draw() { puts("draw orange"); }
};

int main()
{
    Object* orange = new Orange();
    orange->draw();
    return 0;
}

Смысл visitor'а именно в том, чтобы вынести действие из посещаемого класса.
>надо добавить новый метод в базовый класс посетителя и поменять методы объектов, чтобы они новый функционал делали через посетителя

Нет, не нужно ничего добавлять в базовый класс посетителя и менять методы объектов тоже не нужно. В объектах для поддержки паттерна visitor должен быть только один метод accept.
Простой пример — вы пользуетесь паттерном visitor только для рисования, у вас уже есть класс Painter. Захотели добавить сохранение состояния программы — добавляете новый класс Saver, который унаследован от Visitor. В нем находится примерно следующее:
void Saver::visit(Tree& tree)
{
    m_document.addNode("tree");
}

void Saver::visit(Apple& apple)
{
    m_document.addNode("apple");
}

При этом вносить изменения в существующие классы и базовый Visitor не нужно.
Пользоваться Saver'ом нужно следующим образом:
void MainGameClass::saveAllObjects(std::vector<Object*> objects)
{
    for (int i = 0; i < objects.size(); ++i)
    {
        objects[i]->accept(m_saver);
    }
    m_saver.saveToDisk();
}
Разбухать будет только базовый класс Visitor и UpcastVisitor, все остальные визиторы будут реализовывать только то, что им нужно. Это абсолютно нормально.
Если я все правильно понял, вы хотите хранить в каждом объекте как минимум три стратегии — для отрисовки, создания и удаления. С таким подходом в реальном приложении при большом количестве объектов занимаемая память может увеличиться в несколько раз. Паттерн visitor лишен этого недостатка, т.к. в объекте ничего не хранится.
Также при добавлении нового типа стратегии вам нужно будет модифицировать все классы иерархии, добавляя в них вызовы метода этой стратегии. В случае использования паттерна visitor вам просто нужно будет создать новый тип visitor'а, что намного проще.
Спасибо, обновил статью.
Извините, не совсем вас понял. Где дублируется код?
В С++ указатели всегда разыменовываются одним образом. В Java и C# нет указателей, но ситуация там аналогичная и паттерн visitor применяется точно так же. О каких костылях и каких языках идет речь?

Я с вами не согласен, в первую очередь паттерн visitor используется для отделения структуры объектов от алгоритмов, работающих с ней, позволяя добавлять новые методы для работы с классами без модификации этих классов, а так же изменять существующие алгоритмы в одном месте, а не в каждом отдельном классе.
Если вы так напишете, то все перестанет работать, потому что именно из метода accept визитор получает информацию о типе объекта. А если написать painter.visit(*it), то будет вызываться заглушка из базового класса Visitor::visit(Object& object) и ничего не нарисуется.
При появлении новых классов нужно добавить один метод в самый базовый Visitor, с пустой реализацией. Разве это сложно?

Обычно при наличии visitor'ов из классов иерархии выносится большая часть алгоритмов, в этом нет ничего необычного, затем он и нужен, этот паттерн. Да, это вносит некоторую сложность, зато с лихвой окупается гибкостью.
Базовый Visitor всегда знает о всех классах иерархии. По крайней мере о всех листьях. Такой уж это паттерн. Традиционное применение — разделение объектов и алгоритмов для работы с ними. Переносить логику обратно в посещаемый объект противоречит самой идее паттерна.
Обход своих детей — это дополнительная фича, которой, наверное, часто пользуются. Но эта фича никак не противоречит и не мешает работе UpcastVisitor'а. Например, если бы дерево хранило все ветки:
class Tree
{
public:
    virtual void accept(Visitor& visitor)
    {
        for (int i = 0; i < m_branches.size(); ++i)
        {
            m_branches[i].accept(visitor);
        }
        visitor.visit(*this);
    }
    //...
};

Все прекрасно работает, UpcastVisitor посещает все объекты, как и планировалось.
Если у вас в программе большое количество visitor'ов (например 10), и половина из них будет пользоваться UpcastVisitor'ом, то кода будет меньше. Просто я пытался привести простой пример. В UpcastVisitor'e каждый метод всегда состоит всего из одной строчки, его не сложно добавить при создании нового класса.
В UpcastVisitor'е нет ничего из классов Creator и Destroyer, он никак не взаимодействует с библиотекой Box2D, там просто происходит преобразование типов. UpcastVisitor позволяет в Destroyer'e избежать перегрузки четырех методов. Вместо этого перегружается только один — для типа Box2DObject. Любой здравомыслящий программист остановился бы на том, что все эти четыре перегрузки вызывали бы один и тот же метод. Я же пошел дальше и превратил четыре перегрузки в одну.

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность