我需要开发一个C ++解决方案来表示具有特征的对象,其中对象和特征由不同的对象表示,但是关联的实际实现是在派生类中实现的,该派生类用于封装外部实现。我知道这种事情是继承相关问题的典型,所以我希望对正确的解决方案有所了解。实现部分应该被视为一种API边界 - 用户代码不应该看到它,或者只看一次以便选择实现。
以下是一个例子:
#include <cstdio>
// External implementation 1
class SomeShape {};
class SomeBody { public: SomeShape *shape; };
// External implementation 2
class OtherShape {};
class OtherBody { public: OtherShape *shape; };
//////////////
class Shape
{
public:
virtual const char *name() { return "Shape"; }
};
class Body
{
public:
virtual void setShape(Shape *s) = 0;
};
class Factory
{
public:
virtual Shape *makeShape() = 0;
virtual Body *makeBody() = 0;
};
//////////////
class AShape : public Shape
{
public:
SomeShape *someShape;
virtual const char *name() { return "AShape"; }
};
class ABody : public Body
{
protected:
SomeBody *someBody;
AShape *shape;
public:
ABody() { someBody = new SomeBody; }
virtual void setShape(Shape *s)
{
shape = static_cast<AShape*>(s);
printf("Setting shape: %s\n", s->name());
someBody->shape = shape->someShape;
}
};
class AFactory : public Factory
{
public:
virtual Shape *makeShape()
{ return new AShape(); }
virtual Body *makeBody()
{ return new ABody(); }
};
//////////////
class BShape : public Shape
{
public:
OtherShape *otherShape;
virtual const char *name() { return "BShape"; }
};
class BBody : public Body
{
protected:
OtherBody *otherBody;
BShape *shape;
public:
BBody() { otherBody = new OtherBody; }
virtual void setShape(Shape *s)
{
shape = static_cast<BShape*>(s);
printf("Setting shape: %s\n", s->name());
otherBody->shape = shape->otherShape;
}
};
class BFactory : public Factory
{
public:
virtual Shape *makeShape()
{ return new BShape(); }
virtual Body *makeBody()
{ return new BBody(); }
};
因此,上述角色是允许用户实例化Body
和Shape
对象,这些对象可用于管理关联底层实现SomeShape
/ SomeBody
或{ {1}} / OtherShape
。
然后,执行这两种实现的主要功能可以是,
OtherBody
所以,我在这里不满意的部分是int main()
{
// Of course in a real program we would return
// a particular Factory from some selection function,
// this should ideally be the only place the user is
// exposed to the implementation selection.
AFactory f1;
BFactory f2;
// Associate a shape and body in implementation 1
Shape *s1 = f1.makeShape();
Body *b1 = f1.makeBody();
b1->setShape(s1);
// Associate a shape and body in implementation 2
Shape *s2 = f2.makeShape();
Body *b2 = f2.makeBody();
b2->setShape(s2);
// This should not be possible, compiler error ideally
b2->setShape(s1);
return 0;
}
中的static_cast<>
调用,因为它们构建了一个假设,即传递了正确的对象类型,没有任何编译时类型检查。同时,setShape()
可以接受任何setShape()
,而实际上此处只接受派生类。
但是,如果我希望用户代码在Shape
/ Body
级别而不是{{1}级别上运行,我就不知道如何进行编译时类型检查} / Shape
或ABody
/ AShape
级别。但是,切换代码以使BBody
仅接受BShape
将使整个工厂模式无用,并且会强制用户代码知道正在使用哪个实现。
此外,似乎ABody::setShape()
/ AShape*
类是A
/ B
的额外抽象级别,它只是为了在编译时支持它们,但这些并不打算暴露给API,所以重点是......它们只是作为一种阻抗匹配层,强制Some
和Other
进入SomeShape
模具。
但是我的另类选择是什么?可以使用某些运行时类型检查,例如OtherShape
或Shape
,但如果可能的话,我会寻找更优雅的东西。
你会用另一种语言做到这一点?
答案 0 :(得分:0)
分析您的设计问题
您的解决方案实现了abstract factory design pattern,其中包含:
AFactory
和BFactory
是抽象Factory
ABody
和AShape
以及BBody
和BShape
是抽象产品Body
和Shape
的具体产品。 您担心的问题是方法Body::setShape()
取决于抽象形状参数,而具体实现实际上需要具体形状。
正如你正确地指出的那样,向混凝土Shape
的向下倾斜暗示了潜在的设计缺陷。并且不可能在编译时捕获错误,因为整个模式在运行时被设计为动态且灵活的,并且虚拟函数不能被模板化。
备选方案1:让您当前的设计更安全
如果转发有效,请使用dynamic_cast<>
在运行时检查。结果:
备选方案2:采用强隔离设计
更好的设计,就是隔离不同的产品。因此,一个产品类只会使用同一族的其他类的抽象接口,而忽略它们的具体特性。
后果:
Shape*
成员进行分解,甚至可以对setShape()
进行去虚拟化。 备选方案3:模仿依赖类型
选择基于模板的抽象工厂实现。一般的想法是,使用模板实现定义产品之间的内部依赖关系。
因此,在您的示例Shape中,AShape
和BShape
未更改,因为不依赖于其他产品。但Body依赖于形状,广告取决于ABody
AShape
,而BBody
应取决于BShape
。
然后诀窍是使用模板而不是抽象类:
template<class Shape>
class Body
{
Shape *shape;
public:
void setShape(Shape *s) {
shape=s;
printf("Setting shape: %s\n", s->name());
}
};
然后,您可以通过从ABody
Body<AShape>
class ABody : public Body<AShape>
{
protected:
SomeBody *someBody;
public:
ABody() { someBody = new SomeBody; }
};
这一切都非常好,但是如何使用抽象工厂呢?同样的原则:模板化而不是虚拟化。
template <class Shape, class Body>
class Factory
{
public:
Shape *makeShape()
{ return new Shape(); }
Body *makeBody()
{ return new Body(); }
};
// and now the concrete factories
using BFactory = Factory<BShape, BBody>;
using AFactory = Factory<AShape, ABody>;
结果是您必须在编译时知道您打算使用哪种具体工厂和具体产品。这可以使用C ++ 11 auto
:
AFactory f1; // as before
auto *s1 = f1.makeShape(); // type is deduced from the concrete factory
auto *b1 = f1.makeBody();
b1->setShape(s1);
通过这种方法,您将不再能够混合不同家庭的产品。以下语句将导致错误:
b2->setShape(s1); // error: no way to convert an AShape* to a BShape*
这里有 online demo