我需要开发一个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
或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
/ Shape
级别而不是ABody
/ AShape
或BBody
/ 级别。但是,将代码切换为BShape
仅接受ABody::setShape()
会使一方面的整个工厂模式无用,并且将迫使用户代码知道正在使用哪个实现。另外,似乎
AShape*
/ A
类是B
/ Some
的额外抽象级别,它们仅在编译时用于支持它们,但并不打算向API公开。 ,那又有什么意义呢?它们仅用作一种阻抗匹配层,将Other
和SomeShape
都强制进入OtherShape
模具。但是我还有哪些选择?可以使用一些运行时类型检查,例如
Shape
或dynamic_cast<>
,但我正在寻找更优雅的方法(如果可能)。您将如何用另一种语言来做呢?
最佳答案
分析您的设计问题
您的解决方案通过以下方式实现abstract factory design pattern:AFactory
和BFactory
是抽象Factory
的具体工厂
一方面ABody
和AShape
,另一方面BBody
和BShape
是抽象产品Body
和Shape
的具体产品。
Axxx类构成了相关类的一个家族。 Bxxx类也是如此。
您担心的问题是方法Body::setShape()
依赖于抽象的形状参数,而具体的实现实际上期望具体的形状。
正如您正确指出的那样,对具体Shape
的低估表明存在潜在的设计缺陷。而且,由于整个模式在运行时是动态且灵活的,并且无法对虚拟函数进行模板化,因此不可能在编译时捕获错误。
替代方法1:使您当前的设计更安全
使用dynamic_cast<>
在运行时检查向下转换是否有效。后果:
丑陋的铸件被很好地隔离在一个功能上。
仅在必要时(即仅在设置形状时)才进行运行时检查。
备选方案2:采用具有高度隔离性的设计
更好的设计是隔离不同的产品。因此,一个产品类将仅使用同一家族的其他类的抽象接口,而忽略它们的具体特异性。
后果:
非常坚固的设计,实现了关注点的卓越分离
您可以在抽象类级别分解Shape*
成员,甚至可以取消虚拟化setShape()
。
但这是以牺牲刚性为代价的:您无法使用特定于家庭的界面。例如,如果目标是该系列代表一个本机UI,这可能非常令人尴尬,因为他们知道产品高度相互依赖并且需要使用本机API(这是《 4人帮》一书中的典型示例)。
备选方案3:对依赖类型进行模板化
选择基于模板的抽象工厂实现。通常的想法是,使用模板实现定义产品之间的内部依赖关系。
因此,在您的示例Shape中,AShape
和BShape
不变,因为不依赖于其他产品。但是主体取决于形状,您要具有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