带参数化构造函数的抽象工厂

时间:2011-11-27 17:24:46

标签: c++ design-patterns polymorphism abstract-class smart-pointers

我最近听说过抽象工厂模式,目前在需要参数化构造器时如何设计这种模式存在一些疑问。

更准确地说,从我的理解,这个设计模式的主要好处之一 是为了方便代码管理,因为无论何时向系统引入新类型,都需要调整一个工厂函数,在该函数中调用对象构造函数。

我发现的大多数示例都只考虑空构造函数(比如默认构造函数)。
但是,如果需要使用参数化构造函数会发生什么?这种设计模式是否仍然有效? 由于参数在派生类之间的类型和数量可能不同, 是否需要考虑几种工厂功能? 下面我举例说明我想要实现的目标。请注意,为了减少代码行,我只考虑了每个类的一个构造函数,它们既可以作为默认构造函数,也可以作为参数化构造函数。

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

我还有另一个疑问。想象一下,由于某些需要,我需要“ShapeFactory”类的类成员。我想直观地做的是:

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

对于这种特定情况,'ShapeFactory'类声明如何改变?我现在除了param1,param2之外还有一个smart_pointer作为类成员吗?如果是,是否有人可以说明如何实施构造函数/析构函数?

所有的建议/想法都非常受欢迎! ; - )

2 个答案:

答案 0 :(得分:2)

只有当派生对象的签名足够类似于支持公共构造函数签名时,工厂模式才真正适用。这是一种常见的情况,因为对象非常适合共享虚函数签名,因此构造函数同样相似。在您的示例中,从中心点和区域构造形状将适合工厂模式。

如果构造函数完全不相同,那么工厂模式根本没有意义,应该避免。当没有工厂使用时,您提供的使用示例更清晰,更安全。

我将给出一个简短的例子,说明一个合理的工厂,其中包含我最近的代码中的参数:假设您想要将函数拟合到图像的像素。有两种可能的计算方式,不相交(行和列彼此分开)和关节。不相交的拟合更便宜,但某些数据不可能。这些计算方式由两个不同的类支持,DisjointFitter和JointFitter,它们接收数据作为参数,并且都来自Fitter。工厂看起来像:

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

在有些人为的形状方面,它可能看起来像:

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

,抽象工厂方法如下:

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

答案 1 :(得分:0)

当你试图在给定一组盲输入数据的情况下构建一组类中时,工厂模式才有意义。

例如,假设您正在为几何创建XML加载器,并且在循环期间最终处理<Shapes>元素的子项。此时,您将拥有一个通用的Element对象,无需进一步查询即可。这是你可以将Element*传递给返回Shape*的工厂函数的地方,这个想法是加载器可以继续使用生成的Shape*,同时保持忘记什么Shape实际上已经成功了。

这是关键概念:就加载器而言,它被赋予Element*,它可以传递给工厂以接收Shape*,而不需要进一步做出决策。事实上,其他任何人都可以做到这一点,而不仅仅是装载机。

你的问题在于你决定在std::string上对你的工厂进行密钥修改(假设字符串只是形状类型)是不足以完全构造对象的信息。对于您的应用,您必须决定是否有办法为工厂中的所有类通常指定所有可能的必要构造参数。如果没有,那么你就不需要工厂了。

相关问题