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

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
0
16 715
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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 рисования - и, возможно, быть более продвинутым, чем этот (другие цветовые пространства и т. д.).

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

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

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

В чем смысл конструктора копирования по ссылке в вашем коде?

Benoît 01.02.2011 10:51

Это называется конструктором копирования и будет вызываться в неожиданные моменты. Поскольку код написан, в этом нет особого смысла, кроме как сказать, что когда я копирую перо, я копирую цвет пера. Если бы Color был более сложным, это могло бы иметь гораздо большее значение.

gnud 01.02.2011 12:03

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

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

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

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

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

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

When you know at compile time (i.e when you write code) that you want a specific type returned, then use a template. Otherwise, you can't.

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

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

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

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

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

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

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 >());
}

Другой способ - динамически зарегистрировать функцию создатель в динамическом объекте 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 будет заключаться в хранении и поиске этих функций создатель. Остальное оставляю как упражнение;) т.е. использование макроса METADECL

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

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

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

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

user331006 22.09.2010 20:16

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

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

Другие вопросы по теме