通过父母在并行继承层次结构中关联孩子

时间:2016-03-08 18:27:04

标签: c++ inheritance abstract-factory

我需要开发一个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(); }
};

因此,上述角色是允许用户实例化BodyShape对象,这些对象可用于管理关联底层实现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}级别上运行,我就不知道如何进行编译时类型检查} / ShapeABody / AShape级别。但是,切换代码以使BBody仅接受BShape将使整个工厂模式无用,并且会强制用户代码知道正在使用哪个实现。

此外,似乎ABody::setShape() / AShape*类是A / B的额外抽象级别,它只是为了在编译时支持它们,但这些并不打算暴露给API,所以重点是......它们只是作为一种阻抗匹配层,强制SomeOther进入SomeShape模具。

但是我的另类选择是什么?可以使用某些运行时类型检查,例如OtherShapeShape,但如果可能的话,我会寻找更优雅的东西。

你会用另一种语言做到这一点?

1 个答案:

答案 0 :(得分:0)

分析您的设计问题

您的解决方案实现了abstract factory design pattern,其中包含:

  • AFactoryBFactory是抽象Factory
  • 的具体工厂 另一方面,
  • ABodyAShape以及BBodyBShape是抽象产品BodyShape的具体产品。
  • Axxx类构成了相关类的家族。 Bxxx类也是如此。

您担心的问题是方法Body::setShape()取决于抽象形状参数,而具体实现实际上需要具体形状。

正如你正确地指出的那样,向混凝土Shape的向下倾斜暗示了潜在的设计缺陷。并且不可能在编译时捕获错误,因为整个模式在运行时被设计为动态且灵活的,并且虚拟函数不能被模板化。

备选方案1:让您当前的设计更安全

如果转发有效,请使用dynamic_cast<>在运行时检查。结果:

  • 丑陋的演员在一个单一的功能中被很好地隔离。
  • 运行时检查仅在必要时进行,即仅设置形状。

备选方案2:采用强隔离设计

更好的设计,就是隔离不同的产品。因此,一个产品类只会使用同一族的其他类的抽象接口,而忽略它们的具体特性。

后果:

  • 非常强大的设计,可以实现卓越的关注点分离
  • 您可以在抽象类级别对Shape*成员进行分解,甚至可以对setShape()进行去虚拟化。
  • 但这是因为刚性的代价:你无法利用特定于家庭的界面。这可能非常令人尴尬,例如,如果目标是家族代表本地用户界面,知道产品高度相互依赖并且需要使用原生API(这是Gang of 4中典型的例子) 。

备选方案3:模仿依赖类型

选择基于模板的抽象工厂实现。一般的想法是,使用模板实现定义产品之间的内部依赖关系。

因此,在您的示例Shape中,AShapeBShape未更改,因为不依赖于其他产品。但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