Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Abstract Factory with parameterized constructors

I've heard recently about the Abstract Factory Pattern and have currently some doubts on how to design such pattern when parameterized constructors are needed.

More precisely, from what I understood, one of the main benefits of this Design Pattern is to facilitate the code management, since whenever a new type is introduced to the system, one needs to adapt one factory function in which the objects constructors are called.

Most of the examples that I found consider only empty constructors (say default constructors).
But, what would happen if one needs to use Parameterized Constructors? Does this design pattern still works?
Since the parameters may differ in type and quantity between the derived classes, does one need to consider several factory functions? Below I'm giving an example on what I would like to achieve. Note that for reducing the lines of code, I've only considered one constructor per class that serve both as default and parameterized constructor.

class Shape {
public:
    Shape(){std::cout << "Calling Shape Constructor\n";};
    virtual ~Shape(){std::cout << "Calling Shape Desstructor\n";};
    virtual void draw() const = 0;
    virtual void doSomething1(int) const = 0;
    virtual void doSomething2(int, float) const = 0;
};

class Rectangle : public Shape {
public:
    Rectangle(int l = 0, int b = 0 ):l(l),b(b){ std::cout << "Calling Rectangle Constructor\n"; };
    ~Rectangle(){ std::cout << "Calling Rectangle Destructor\n\n"; };
    virtual void draw() const{ /* Draw Rectangle */ }; 
    virtual void doSomething1(int) const { /* doSomething1 */};
    virtual void doSomething2(int, float) const { /* doSomething2 */};
private:
    int l,b;
};

class Circle : public Shape {
public:
    Circle(int r = 0):r(r){ std::cout << "Calling Circle Constructor\n"; };
    ~Circle(){ std::cout << "Calling Rectangle Destructor\n\n"; };
    virtual void draw() const{ /* Draw Circle*/ }; 
    virtual void doSomething1(int) const { /* doSomething1 */};
    virtual void doSomething2(int, float) const { /* doSomething2 */};
private:
    int r;
};

class ShapeFactory{

public:
    ShapeFactory(int = 0, double = 0);
    std::unique_ptr<Shape> CreateShape(const std::string & );
    ~ShapeFactory();
};

std::unique_ptr<Shape> ShapeFactory::CreateShape(const std::string & type /*, int rad, int side1, int side2, .... */) {

    if ( type == "circle" ) return std::unique_ptr<Shape>(new Circle( /* rad */)); // Should call Circle(int rad)!
    if ( type == "rectangle" ) return std::unique_ptr<Shape>(new Rectangle( /* side1, side2 */)); // Should call Rectangle(int, int)!
    // if ( type == "someNewShape") return std::unique_ptr<Shape>(new someNewShape( /* param1, param2, ... */)); // Should call someNewShape(param1, param2)!
    throw std::invalid_argument("MobileFactory: invalid type: " + type);
}

I've also further another doubt. Imagine that because of some needs, I need class members for the "ShapeFactory" class. What I would like to intuitively do, is something like:

std::vector< std::unique_ptr<ShapeFactory2> > mylist;
mylist.push_back( new ShapeFactory2(CreateShape("circle",radius), param1, param2) );
mylist.push_back( new ShapeFactory2(CreateShape("rectangle",side1,side2), param1, param2) );

for (std::vector< std::unique_ptr<ShapeFactory2> >::const_iterator it = v.begin(), end = v.end(); it != end; ++it)
{
    int param1   = it->param1;
    float param2 = it->param2;
    it->doSomething2(param1, param2);

    // or equivalently 
    Shape * myShape = *it;
    int param1   = it->param1;
    float param2 = it->param2;
    myShape->doSomething2(param1, param2);
}

How does the 'ShapeFactory' class declaration would change for this specific case? Would I've now a smart_pointer as class member besides param1, param2? If yes, could anyone illustrate how to implement the Constructors/Destructors?

All suggestions/ideas are really welcomed! ;-)

like image 200
Tin Avatar asked Nov 04 '22 10:11

Tin


1 Answers

The factory pattern is only really applicable when the signatures of the derived objects are sufficiently similar to support a common constructor signature. That's a common case because the objects are suitable alike to share a virtual function signature, so the constructors are similarly alike. In your example, constructing shapes from a center point and an area would be suitable for a factory pattern.

If the constructors are not alike at all, the factory pattern simply makes no sense and should be avoided. The usage example you provided is much clearer and safer when no factories are used.

I'll give you a short example for a sensible factory with arguments from my recent code: Suppose you want to fit a function to the pixels of an image. There are two possible computation ways, disjoint (rows and columns apart from each other) and joint. Disjoint fitting is cheaper, but not possible for some data. These computation ways are supported by two different classes, DisjointFitter and JointFitter, which receive the data as their argument and both derive from Fitter. The factory looks like:

std::auto_ptr<Fitter> Fitter::create( const Data& data ) {
    if ( data.supports_disjoint_fitting() ) {
        return std::auto_ptr<Fitter>( new DisjointFitter(data) );
    } else {
        return std::auto_ptr<Fitter>( new JointFitter(data) );
    }
}

In the somewhat contrived terms of the shapes, it might look like:

enum BasicShape { Round, Edgy };
mylist.push_back( ShapeFactory::CreateShape( Round, 16 ) );

and the abstract factory method would look like:

static std::unique_ptr<Shape> CreateShape(BasicShape shape, double area) {
    if ( shape == Round ) 
        return std::unique_ptr<Shape>( new Circle( sqrt(area / M_PI) ) );
    else
        return std::unique_ptr<Shape>( new Square( sqrt(area) ) );
}
like image 121
thiton Avatar answered Nov 11 '22 07:11

thiton