Реализация метода фабрики - C ++ - PullRequest
3 голосов
/ 04 января 2009

У меня есть следующий код для реализации "фабричного" шаблона проектирования.

class Pen{
public:
     virtual void Draw() = 0;
};

class RedPen : public Pen{
public:
     virtual void Draw(){
         cout << "Drawing with red pen" << endl;
     }
};

class BluePen : public Pen{
public:
     virtual void Draw(){
         cout << "Drawing with blue pen" << endl;
     }
};

auto_ptr<Pen> createPen(const std::string color){
     if(color == "red")
         return auto_ptr<Pen>(new RedPen);
     else if(color == "blue")
         return auto_ptr<Pen>(new BluePen);
}

Но я слышал, что это можно сделать лучше, используя "шаблоны C ++". Может кто-нибудь помочь, как это делается и как шаблонный подход лучше, чем этот?

Любые мысли

Ответы [ 6 ]

12 голосов
/ 11 февраля 2009

Другой способ - динамически зарегистрировать функцию создатель в динамическом объекте Factory.

BluePen *create_BluePen() { return new BluePen; }
static bool BluePen_creator_registered = 
                       Factory::instance()->registerCreator("BluePen", 
                                                            create_BluePen);

Один интересный эффект в этом случае состоит в том, что статическая переменная bool BluePen-creator-registered будет установлена ​​до начала main(), что делает регистрацию автоматической.

Эти строки иногда создаются с помощью обычных макросов, например

#define METAIMPL( _name ) \
_name *create_ ## _name() { return new _name; } \
static bool _name ## _creator_registered = \
                        Factory::instance()->registerCreator(# _name, \
                                                             create_ ## _name)

... и используется близко к конструктору

METAIMPL( BluePen ); // auto registers to the Factory

BluePen::BluePen() : Pen() {
   // something
}

Тогда задача фабрики будет хранить и искать эти создатель функций. Остальное я оставляю как упражнение ;) т.е. использование макроса METADECL

Если вам нужна дополнительная информация, см. здесь в главе 4,1 метаинформации , которая также включает метод расширения, включающий возможности для инспектора функций

Я узнал об этом с помощью ET ++ , который был проектом для переноса старого MacApp на C ++ и X11. Эрик Гамма и другие начали думать о шаблонах проектирования

И ... (7 мая 2011 г.) Наконец-то пришёл пример github
https://github.com/epatel/cpp-factory

5 голосов
/ 26 января 2009

Ваш завод в порядке. Я понимаю, что BluePen и т. Д. Были просто примерами имен классов. Вы можете использовать шаблоны, если выполнено следующее условие:

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

Это означает, что в коде вы можете сделать это:

template<typename PenType>
auto_ptr<Pen> createPen(){
    return auto_ptr<Pen>(new PenType);
}

Имея это на месте, вы можете использовать это как

...
auto_ptr<Pen> p = createPen<BluePen>();
...

Но этот аргумент шаблона, BluePen, не может быть переменной, для которой задан тип во время выполнения. В вашем примере вы передаете строку, которая, конечно, может быть установлена ​​во время выполнения. Итак, когда вы читаете, что можете использовать шаблоны C ++, тогда эта рекомендация является только условно верной - тогда, когда ваше решение о том, какое перо создавать, уже сделано во время компиляции. Если это условие подходит, тогда шаблонное решение правильное решение. Это не будет стоить вам ничего во время выполнения и будет именно тем, что вам нужно.

4 голосов
/ 04 января 2009

В приведенном вами примере ни заводской, ни шаблонный подход не имеют смысла для меня. Мое решение включает член данных в классе Pen.

class Pen {
public:
    Pen() : m_color(0,0,0,0) /* the default colour is black */
    {            
    }

    Pen(const Color& c) : m_color(c)
    {
    }

    Pen(const Pen& other) : m_color(other.color())
    {
    }

    virtual void Draw()
    {
        cout << "Drawing with a pen of color " << m_color.hex();
    }
    void setColor(const Color& c) { m_color = c; }
    const Color& color() const { return m_color; }
private:
    Color m_color;
};

class Color {
public:
    Color(int r, int g, int b, int a = 0) :
        m_red(r), m_green(g), m_blue(other.blue()), m_alpha(a)  
    {
    }

    Color(const Color& other) : 
        m_red(other.red()), m_green(other.green()), 
        m_blue(other.blue()), m_alpha(other.alpha())
    {
    }

    int red() const { return m_red; }
    int green() const  { return m_green; }
    int blue() const { return m_blue; }
    int alpha() const { return m_alpha; }

    std::string hex() const
    {
        std::ostringstream os;
        char buf[3];
        os << "#";

        sprintf(buf, "%2X", red());
        os << buf;

        sprintf(buf, "%2X", green());
        os << buf;

        sprintf(buf, "%2X", blue());
        os << buf;

        sprintf(buf, "%2X", alpha());
        os << buf;

        return os.str();
    }

private:
    int m_red;
    int m_green;
    int m_blue;
    int m_alpha;
}

Конечно, цветовой класс должен быть скорректирован в соответствии с используемым API-интерфейсом рисования, и, возможно, он будет более продвинутым, чем этот (различные цветовые пространства и т. Д.).

Почему не шаблоны?

Причина, по которой нет смысла использовать шаблоны, заключается в том, что (предположительно) единственное различие между различными операциями рисования - это переменная цвета. Таким образом, используя шаблоны (или вручную объявляя различные классы, как вы это сделали), вы дублируете похожий код. Это сделает вашу программу большой и замедлит ее.

Таким образом, функция рисования должна либо принимать цвет в качестве аргумента, либо (как в моем примере) иметь цвет в качестве члена данных класса.

3 голосов
/ 30 января 2009

Объявляя специальные пустые классы для цветов, вы можете делать все, используя шаблоны. Это требует, чтобы каждый выбор цвета был доступен во время компиляции. Делая это, вы избегаете необходимости использовать базовый класс с виртуальными методами.

struct Red{};
struct Blue{};

template < typename Color >
class Pen{};

template <>
class Pen< Red >
{
     void Draw(){
         cout << "Drawing with red pen" << endl;
     }
};

template <>
class Pen< Blue >
{
     void Draw(){
         cout << "Drawing with blue pen" << endl;
     }
};

template < typename Color >
std::auto_ptr< Pen< Color > > createPen()
{
     return auto_ptr< Pen< Color > >(new Pen< Color >());
}
1 голос
/ 01 февраля 2012

Вы можете написать универсальный класс фабрики объектов в качестве класса шаблона (или использовать класс, хорошо описанный в этой статье gamedev.net ).

Таким образом, если в вашем коде более одной фабрики, определить каждую фабрику будет труднее.

0 голосов
/ 04 января 2009

В качестве дополнения к моему другому ответу, просто для обсуждения шаблона фабрики и использования шаблона:

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

Даже когда код отличается, не только типами данных, возможно использовать специализации шаблонов - но во многих случаях заводская функция имеет больше смысла.

В качестве примера рассмотрим библиотеку абстракций базы данных. Можно было бы использовать специализации шаблонов, чтобы библиотека могла использоваться как "db :: driver". Но это вынудит вас печатать тип базы данных повсюду в коде (в первую очередь делая библиотеку бесполезной ...) или выполнять случай с типом интерфейса db :: driver class.

В этом примере более интуитивно понятно сказать db :: get_driver (odbc) и вернуть правильный класс, приведенный к типу интерфейса.

...