好吧,我在C ++中有一个有点复杂的系统。简而言之,我需要向第三方抽象基类添加方法。第三方还提供了大量的派生类,这些类也需要新的功能。
我正在使用一个提供标准Shape接口的库,以及一些常见的形状。
class Shape
{
public:
Shape(position);
virtual ~Shape();
virtual position GetPosition() const;
virtual void SetPosition(position);
virtual double GetPerimeter() const = 0;
private: ...
};
class Square : public Shape
{
public:
Square(position, side_length);
...
};
class Circle, Rectangle, Hexagon, etc
现在,这是我的问题。我希望Shape类还包含GetArea()函数。所以看起来我应该做一个:
class ImprovedShape : public virtual Shape
{
virtual double GetArea() const = 0;
};
class ImprovedSquare : public Square, public ImprovedShape
{
...
}
然后我去制作一个继承自ImprovedShape和Square的ImprovedSquare。好吧,正如你所看到的,我现在已经创建了可怕的diamond inheritance problem。如果第三方库使用virtual inheritance作为Square,Circle等,这很容易解决。但是,让他们这样做是不合理的选择。
那么,当您需要向库中定义的接口添加一些功能时,您会怎么做?有一个很好的答案吗?
谢谢!
答案 0 :(得分:7)
为什么这个类需要从形状派生?
class ImprovedShape : public virtual Shape
{
virtual double GetArea() const = 0;
};
为什么不拥有
class ThingWithArea
{
virtual double GetArea() const = 0;
};
ImprovedSquare是一个Shape,是一个ThingWithArea
答案 1 :(得分:4)
我认为facade模式应该可以解决问题。
将第三方界面包装到您自己的界面中,您的应用程序代码与包装界面而不是第三方界面一起使用。这样你就可以很好地隔离不受控制的第三方界面的变化。
答案 2 :(得分:4)
我们在一个项目中遇到了一个非常类似的问题,我们只是从Shape中派生出ImprovedShape来解决它。如果你需要在ImprovedShape中使用Shape功能,你可以使用dynamic_cast,因为你知道你的演员阵容将永远有效。其余的就像你的例子一样。
答案 3 :(得分:4)
也许您应该阅读proper inheritance,并得出结论,ImprovedShape不需要从Shape继承,而是可以使用Shape作为其绘图功能,类似于关于SortedList的FAQ的第21.12节中的讨论即使它想要提供相同的功能,它也不必从List继承,它只需使用一个List。
以类似的方式,ImprovedShape可以使用一个Shape来做它的形状。
答案 4 :(得分:2)
答案 5 :(得分:1)
是否可以采用完全不同的方法 - 使用模板和元编程技术?如果您不限制不使用模板,这可以提供一个优雅的解决方案。只有ImprovedShape
和ImprovedSquare
更改:
template <typename ShapePolicy>
class ImprovedShape : public ShapePolicy
{
public:
virtual double GetArea();
ImprovedShape(void);
virtual ~ImprovedShape(void);
protected:
ShapePolicy shape;
//...
};
并且ImprovedSquare
变为:
class ImprovedSquare : public ImprovedShape<Square>
{
public:
ImprovedSquare(void);
~ImprovedSquare(void);
// ...
};
您将避免钻石继承,从原始Shape(通过策略类)获取继承以及您想要的附加功能。
答案 6 :(得分:1)
GetArea()
分隔到自己的界面中:
class ThingWithArea
{
public:
virtual double GetArea() const = 0;
};
如果Shape的设计师做了正确的事情并使其成为纯粹的界面,
并且具体类的公共接口足够强大,你可以
将具体类的实例作为成员。这就是你得到的SquareWithArea
(ImprovedSquare
名称很差)是Shape
和ThingWithArea
:
class SquareWithArea : public Shape, public ThingWithArea
{
public:
double GetPerimeter() const { return square.GetPerimeter(); }
double GetArea() const { /* do stuff with square */ }
private:
Square square;
};
不幸的是,Shape
设计师将一些实现放入Shape
和你
每个SquareWithArea
最终会携带两份副本,就像在里面一样
你最初提出的钻石。
这几乎迫使你进入最紧密耦合,因此最少 理想的解决方案:
class SquareWithArea : public Square, public ThingWithArea
{
};
现在,从C ++中的具体类派生它被认为是不好的形式。
很难找到一个非常好的解释为什么你不应该。通常,人
引用Meyers的更有效的C ++第33项,它指出了不可能性
写一篇体面的operator=()
等等。那么,你应该这么做
永远不要为具有价值语义的类做这件事。另一个陷阱是
具体类没有虚拟析构函数(这就是你应该这样做的原因
永远不会公开源自STL容器)。这两个都不适用。海报
谁居高临下地送你到C ++ faq来了解继承是
错误 - 添加GetArea()
并不违反Liskov的可替代性。关于
我能看到的唯一风险来自覆盖虚拟功能
具体的类,当实现者稍后更改名称并默默地中断时
你的代码。
总之,我认为你可以从良心中获得Square。 (作为安慰,您不必编写所有转发功能 Shape接口)。
现在针对需要两个接口的功能问题。我不喜欢
不必要的dynamic_cast
。相反,让函数接受引用
两个接口并在调用站点传递对同一对象的引用:
void PrintPerimeterAndArea(const Shape& s, const ThingWithArea& a)
{
cout << s.GetPerimeter() << endl;
cout << a.GetArea() << endl;
}
// ...
SquareWithArea swa;
PrintPerimeterAndArea(swa, swa);
所有PrintPerimeterAndArea()
需要做的工作是外围和a的来源
面积来源。这些碰巧实施并不是它的担忧
作为同一对象实例上的成员函数。可以想象,该地区可以
由它和Shape
之间的一些数值积分引擎提供。
这让我们得到了唯一可以考虑传递一个参考的情况
并通过dynamic_cast
获得另一个 - 两者的重要性
引用是指同一个对象实例。这是一个非常人为的例子:
void hardcopy(const Shape& s, const ThingWithArea& a)
{
Printer p;
if (p.HasEnoughInk(a.GetArea()))
{
s.print(p);
}
}
即便如此,我可能更愿意发送两个引用而不是
dynamic_cast
。我会依靠一个理智的整体系统设计来消除
两个不同实例的位的可能性被馈送到这样的函数。
答案 7 :(得分:1)
对元编程/ mixin的另一种看法,这次受到特征的影响。 它假定计算区域是您想要根据暴露的属性添加的东西;你可以做一些保持封装的东西,这是一个目标,而不是模块化。但是你必须为每个子类型编写一个GetArea,而不是在可能的情况下使用多态。这是否值得取决于你对封装的承诺程度,以及你的库中是否有基类可以利用常见的行为,例如 RectangularShape
#import <iostream>
using namespace std;
// base types
class Shape {
public:
Shape () {}
virtual ~Shape () { }
virtual void DoShapyStuff () const = 0;
};
class RectangularShape : public Shape {
public:
RectangularShape () { }
virtual double GetHeight () const = 0 ;
virtual double GetWidth () const = 0 ;
};
class Square : public RectangularShape {
public:
Square () { }
virtual void DoShapyStuff () const
{
cout << "I\'m a square." << endl;
}
virtual double GetHeight () const { return 10.0; }
virtual double GetWidth () const { return 10.0; }
};
class Rect : public RectangularShape {
public:
Rect () { }
virtual void DoShapyStuff () const
{
cout << "I\'m a rectangle." << endl;
}
virtual double GetHeight () const { return 9.0; }
virtual double GetWidth () const { return 16.0; }
};
// extension has a cast to Shape rather than extending Shape
class HasArea {
public:
virtual double GetArea () const = 0;
virtual Shape& AsShape () = 0;
virtual const Shape& AsShape () const = 0;
operator Shape& ()
{
return AsShape();
}
operator const Shape& () const
{
return AsShape();
}
};
template<class S> struct AreaOf { };
// you have to have the declaration before the ShapeWithArea
// template if you want to use polymorphic behaviour, which
// is a bit clunky
static double GetArea (const RectangularShape& shape)
{
return shape.GetWidth() * shape.GetHeight();
}
template <class S>
class ShapeWithArea : public S, public HasArea {
public:
virtual double GetArea () const
{
return ::GetArea(*this);
}
virtual Shape& AsShape () { return *this; }
virtual const Shape& AsShape () const { return *this; }
};
// don't have to write two implementations of GetArea
// as we use the GetArea for the super type
typedef ShapeWithArea<Square> ImprovedSquare;
typedef ShapeWithArea<Rect> ImprovedRect;
void Demo (const HasArea& hasArea)
{
const Shape& shape(hasArea);
shape.DoShapyStuff();
cout << "Area = " << hasArea.GetArea() << endl;
}
int main ()
{
ImprovedSquare square;
ImprovedRect rect;
Demo(square);
Demo(rect);
return 0;
}
答案 8 :(得分:1)
GetArea()不必是成员。它可以是模板化函数,因此您可以为任何形状调用它。
类似的东西:
template <class ShapeType, class AreaFunctor>
int GetArea(const ShapeType& shape, AreaFunctor func);
可以将STL min,max函数视为您案例的类比。给定比较器函数,您可以找到对象的数组/向量的最小值和最大值。同样明智的是,你可以得出任何给定形状的面积,只要有计算面积的函数。
答案 9 :(得分:0)
正如我所理解的那样,你的问题存在解决方案。使用addapter-pattern。适配器模式用于向特定类添加功能或交换特定行为(即方法)。考虑你画的场景:
class ShapeWithArea : public Shape
{
protected:
Shape* shape_;
public:
virtual ~ShapeWithArea();
virtual position GetPosition() const { return shape_->GetPosition(); }
virtual void SetPosition(position) { shape_->SetPosition(); }
virtual double GetPerimeter() const { return shape_->GetPerimeter(); }
ShapeWithArea (Shape* shape) : shape_(shape) {}
virtual double getArea (void) const = 0;
};
Adapter-Pattern旨在调整类的行为或功能。你可以用它来
它如何改变行为?当您向方法提供类型为base的对象时,您也可以提供适应的类。对象将按照您的指示运行,对象上的actor只关心基类的接口。 您可以将此适配器应用于Shape的任何派生。