界面与构图

Aar*_*ron 4 c++ abstract-class interface composition

我想我理解界面和抽象之间的区别.Abstract设置默认行为,在纯抽象的情况下,行为需要由派生类设置.接口是您需要的,无需基类的开销.那么界面优于作曲的优势是什么?我能想到的唯一优势是在基类中使用受保护的字段.我错过了什么?

Mat*_* M. 8

你的标题没有意义,你的解释有点模糊,所以让我们定义术语(并介绍缺少的关键)。

这里发生了两件不同的事情:

  • 抽象类与接口
  • 继承与组合

让我们从接口和抽象类开始。

  • 一个抽象类(在C ++中)是不能被实例化,因为至少有一个其方法是一个纯虚拟方法的类。
  • 一个接口,在Java类语言,是一组没有实现方法,在C ++中它是模拟与抽象类用纯虚函数。

因此,在 C++ 的上下文中,两者之间没有太大区别。特别是因为这种区别从未考虑过自由函数。

例如,考虑以下“接口”:

class LessThanComparable {
public:
    virtual ~LessThanComparable() {}

    virtual bool less(LessThanComparable const& other) const = 0;
};
Run Code Online (Sandbox Code Playgroud)

即使使用免费功能,您也可以轻松地增加它:

inline bool operator<(LessThanComparable const& left, LessThanComparable const& right) {
    return left.less(right);
}

inline bool operator>(LessThanComparable const& left, LessThanComparable const& right) {
    return right.less(left);
}

inline bool operator<=(LessThanComparable const& left, LessThanComparable const& right) {
    return not right.less(left);
}

inline bool operator>=(LessThanComparable const& left, LessThanComparable const& right) {
    return not left.less(right);
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们提供行为......但类本身仍然是一个接口......哦,好吧。


因此,真正的争论是在继承组合之间。

继承经常被误用于继承行为。这不好。应该使用继承来建模is-a关系。否则,您可能需要 Composition。

考虑一个简单的用例:

class DieselEngine { public: void start(); };
Run Code Online (Sandbox Code Playgroud)

现在,我们如何Car用这个构建一个?

如果您继承,它将起作用。然而,突然你得到这样的代码:

void start(DieselEngine& e) { e.start(); }

int main() {
    Car car;
    start(car);
}
Run Code Online (Sandbox Code Playgroud)

现在,如果您决定替换DieselEngineWaterEngine,则上述功能不起作用。编译失败。并且WaterEngine继承自DieselEngine当然感觉不舒服......

那有什么解决办法呢?组成

class Car {
public:
    void start() { engine.start(); }

private:
    DieselEngine engine;
};
Run Code Online (Sandbox Code Playgroud)

这样,没有人可以编写假设汽车是发动机的无意义代码(doh!)。因此,更换发动机很容易,绝对不会对客户产生影响

这意味着您的实现和使用它的代码之间的依从性较低;或者通常所说的:减少耦合


经验法则是,一般来说,从具有数据或实现行为的类继承应该是不受欢迎的。它可以是合法的,但通常有更好的方法。当然,就像所有的经验法则一样,它是一粒盐。小心过度设计。


Pet*_*ood 6

接口定义了您将如何使用。

你继承是为了被重用。这意味着您想要适应某个框架。如果您不需要适应框架,即使是您自己制作的框架,也不要继承。

组合是一个实现细节。不要为了获得基类的实现而继承,组合它。仅当它允许您适应框架时才继承。


Gaz*_*yer 5

接口定义行为.抽象类有助于实现行为.

从理论上讲,完全没有实现的抽象类与接口之间没有太大区别.两者都定义了一个未实现的API.但是,纯抽象类通常用于不支持接口的语言,以提供类似语义的接口(例如C++).

当您有选择时,通常抽象基础将提供某种程度的功能,即使它不完整.它有助于实现常见行为.不利的一面是你被迫从中衍生出来.当您只是定义用法时,请使用界面.(没有什么能阻止你创建一个实现接口的抽象基础).