Factory method implementation - C++

2019-01-11 12:03发布

问题:

I have the following code for "factory" design pattern implementation.

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

But I heard that it can be done in a better way using "C++ templates". Can anyone help how it is done and how template approach is better than this?

Any thoughts

回答1:

In the example you posted, neither a factory or a template approach makes sense to me. My solution involves a data member in the Pen class.

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

Of course, the color class would have to be adjusted to the drawing API you use -- and perhaps be way more advanced than this one (different color spaces, etc).

Why not templates?

The reason it does not make sense to use templates, is that (presumably) the only difference between the different drawing operations is the color variable. So, by using templates (or manually declaring different classes, as you did), you will duplicate similar code. This will make your program large, and slow it down.

So, the draw function should either take the color as an argument, or (as in my example) have the color as a class data member.



回答2:

Another way is to dynamically register a creator function to a dynamical Factory object.

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

One interesting effect in doing like this is that the static bool variable BluePen-creator-registered will be set prior main() starts thus making the registering automated.

These lines are sometimes made through ordinary macros, ie as

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

...and used close to the constructor

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

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

Then the Factory's task will be to store and lookup these creator functions. I leave the rest as the exercise ;) ie the use of a METADECL macro

If you want more info, see here under chapter 4.1 Meta Information which also includes a method for expanding to include possibilities for inspector features

I learnt this from using ET++ that was a project to port old MacApp to C++ and X11. In the effort of it Eric Gamma etc started to think about Design Patterns

And...(May 7 2011) Finally came around to push an example to github
https://github.com/epatel/cpp-factory



回答3:

Your factory is fine. I take it the BluePen and so on were just example class names. You can use templates, if the following condition is met:

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.

That means in code, that you can do this:

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

Having that in place, you can then use that like

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

But that template-argument, the BluePen, can't be a variable that is set to a type at runtime. In your example, you pass a string, which can of course be set at runtime. So, when you read that you can use C++ Templates, then that recommendation is only conditionally true - then, when your decision, of which pen to create, is already done at compile time. If that condition fits, then the template solution is the right thing to do. It will not cost you anything at runtime, and will be exactly what you need.



回答4:

By declaring special empty classes for colors, you can do everything using templates. This requires every color choice to be made available at compile-time. By doing this, you avoid having to use a base class with virtual methods.

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


回答5:

You could write a generic object factory class as a template class (or use the one nicely described in this gamedev.net article).

This way, if you have more than one factory in your code, it is less work to define each factory.



回答6:

As a supplement to my other answer, just to discuss the Factory pattern vs. template useage:

The main (and simplest) reason to use templates, is that your code is identical in every case, except for the data types it works on. Examples here are the STL containers. It would be possible to write a factory function createVector("string"), and type out each container manually -- but this is clearly sub-optimal.

Even when code differs, not only data types, it's possible to use template specializations -- but in many cases, a factory function would make more sense.

As an example, consider a database abstraction library. It would be possible to use template specializations, so that the library could be used like "db::driver". But this would force you to type out the database type everywhere in the code (making the library kinda useless in the first place...), or perform a case to an interface type db::driver class.

In this example, it's more intuitive to say db::get_driver(odbc) and get back the proper class cast to the interface type.