Реализация фабричного метода - C ++

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

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 ++». Может ли кто-нибудь помочь, как это делается и чем шаблонный подход лучше этого?

Есть предположения


person Navaneeth K N    schedule 04.01.2009    source источник


Ответы (6)


В опубликованном вами примере для меня не имеет смысла ни фабричный, ни шаблонный подход. Мое решение включает член данных в классе 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 рисования - и, возможно, быть более продвинутым, чем этот (другие цветовые пространства и т. Д.).

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

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

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

person gnud    schedule 04.01.2009
comment
В чем смысл конструктора копирования по ссылке в вашем коде? - person Benoît; 01.02.2011
comment
Это называется конструктором копирования и будет вызываться в неожиданные моменты. Поскольку код написан, в нем нет особого смысла, кроме как сказать, что когда я копирую перо, я копирую цвет пера. Если бы Color был более сложным, это могло иметь гораздо большее значение. - person gnud; 01.02.2011

Другой способ - динамически зарегистрировать функцию creator в динамическом объекте 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
}

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

Если вам нужна дополнительная информация, см. здесь в разделе < em> 4.1 Мета-информация, которая также включает метод расширения, чтобы включить возможности для функций Inspector.

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

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

person epatel    schedule 10.02.2009
comment
У меня есть отдельный вопрос SO относительно этого ответа: stackoverflow.com/questions/3770654/ Есть шанс, что вы могли бы взглянуть? Заранее спасибо. - person ; 22.09.2010

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

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

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

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

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

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

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

person Johannes Schaub - litb    schedule 25.01.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 >());
}
person Benoît    schedule 30.01.2009

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

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

person RyanCu    schedule 01.02.2012

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

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

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

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

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

person gnud    schedule 04.01.2009