界面与构图

时间:2012-04-26 13:54:15

标签: c++ interface abstract-class composition

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

5 个答案:

答案 0 :(得分:5)

界面定义行为。抽象类有助于实施行为。

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

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

答案 1 :(得分:4)

你的标题没有意义,你的解释有点模糊,所以让我们定义术语(并引入缺少的一个)。

这里有两件不同的事情:

  • 抽象类与接口
  • 继承与构成

让我们从Interfaces和Abstract Classes开始。

  • 抽象类(在C ++中)是一个无法实例化的类,因为其中至少有一个方法是纯虚方法。
  • 接口,类似于Java的语言,是一组没有实现的方法,在C ++中,它只使用纯虚方法抽象类进行模拟。 / LI>

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

例如,请考虑以下“界面”:

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

    virtual bool less(LessThanComparable const& other) const = 0;
};

即使使用免费功能,您也可以轻松扩充它:

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);
}

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


因此,真正的争论是在继承撰写之间。

继承经常被滥用来继承行为。这是不好的。继承应该用于建模 is-a 关系。否则,你可能想要组合。

考虑简单的用例:

class DieselEngine { public: void start(); };

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

如果你继承,它会起作用。但是,突然间你得到了这样的代码:

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

int main() {
    Car car;
    start(car);
}

现在,如果您决定将DieselEngine替换为WaterEngine,则上述功能无效。编译失败。让WaterEngine继承DieselEngine肯定会感觉到ikky ......

那么解决方案是什么?的组合物

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

private:
    DieselEngine engine;
};

这样,没有人可以编写假设汽车是引擎的无意义代码(doh!)。因此,改变发动机轻松,绝对无客户影响

这意味着您的实现与使用它的代码之间的依从性较低;或者通常被称为:较少耦合


经验法则是,一般来说,从具有数据或实现行为的类继承应该是皱眉头。它可能是合法的,但通常有更好的方法。当然,像所有的经验法则一样,它应该是一粒盐;小心过度工程。

答案 2 :(得分:3)

界面定义了您的使用方式。

您继承以便重复使用。这意味着你想要适应一些框架。如果您不需要适应框架,即使是自己制作的框架,也不要继承。

组合是一个实现细节。为了获得基类的实现,不要继承,编写它。只有在允许你适应框架时才继承。

答案 3 :(得分:0)

接口很薄,在C ++中,它们可以被描述为只有纯虚函数的类。瘦是好的,因为

  • 它减少了使用或实施界面的学习曲线
  • 它减少了用户和接口实现者之间的耦合(依赖性)。因此,用户确实很好地隔离了他们正在使用的接口的实现的变化。

这与动态库链接相结合,有助于促进即插即用,这是近期无名但最好的软件创新之一。这导致更高的软件互操作性,可扩展性等。

接口可以做更多的工作。当你拥有一个可能有多个可能的实现的重要子系统时,证明它们被采用了。在这种情况下,子系统应该通过接口使用。

通过无意识重用需要更多知道您所覆盖的实现的行为,因此存在更大的“耦合”。也就是说,在接口过度的情况下,它也是一种有效的方法。

答案 4 :(得分:0)

如果类型Y继承自类型X,那么知道如何处理类型X的对象的代码在大多数情况下将自动能够处理类型为Y的对象。同样,如果类型Z实现了接口I,那么代码知道如何使用关于哪个实现I的对象,而不必了解它们的任何内容,将自动能够使用Z类型的对象。继承和接口的主要目的是允许这样的替换。

相反,如果P类型的对象包含Q类型的对象,那么期望使用Q类型对象的代码将无法在P类型的对象上工作(除非P继承自Q这种类型的对象)。期望操作类型Q的对象的代码将能够在P中包含的Q实例上操作,但前提是P的代码明确地将其直接提供给该代码,或者使其可用于执行此操作的外部代码。