我想我理解界面和抽象之间的区别。 Abstract设置默认行为,在纯抽象的情况下,行为需要由派生类设置。接口是您需要的,无需基类的开销。那么界面优于作曲的优势是什么?我能想到的唯一优势是在基类中使用受保护的字段。我错过了什么?
答案 0 :(得分:5)
界面定义行为。抽象类有助于实施行为。
理论上,完全没有实现的纯抽象类与接口之间没有太大区别。两者都定义了一个未实现的API。但是,纯抽象类通常用于不支持接口的语言,以提供类似语义的接口(例如C ++)。
当您有选择时,通常抽象基础将提供某种程度的功能,即使它不完整。它有助于实现常见行为。不利的一面是你被迫从中衍生出来。当您只是定义用法时,请使用界面。 (没有什么可以阻止你创建一个实现接口的抽象基础。)
答案 1 :(得分:4)
你的标题没有意义,你的解释有点模糊,所以让我们定义术语(并引入缺少的一个)。
这里有两件不同的事情:
让我们从Interfaces和Abstract Classes开始。
因此,在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的代码明确地将其直接提供给该代码,或者使其可用于执行此操作的外部代码。