【发布时间】:2010-01-23 04:52:45
【问题描述】:
当我与朋友讨论时,我对这些概念感到困惑。
我朋友的意见是
1) 抽象是关于纯虚函数。
2)接口不是成员函数,接口是纯虚函数。
我发现在C++入门中,接口是数据类型支持的那些操作,所以成员函数就是接口。
我的意见是
1) 抽象是关于接口和实现的分离;
2) 成员函数是接口。
那么有人可以为我澄清这些概念吗?
1) 抽象、抽象数据类型和抽象类的区别。
2) 接口和成员函数的区别。
3) 抽象与封装的区别。
【问题讨论】:
当我与朋友讨论时,我对这些概念感到困惑。
我朋友的意见是
1) 抽象是关于纯虚函数。
2)接口不是成员函数,接口是纯虚函数。
我发现在C++入门中,接口是数据类型支持的那些操作,所以成员函数就是接口。
我的意见是
1) 抽象是关于接口和实现的分离;
2) 成员函数是接口。
那么有人可以为我澄清这些概念吗?
1) 抽象、抽象数据类型和抽象类的区别。
2) 接口和成员函数的区别。
3) 抽象与封装的区别。
【问题讨论】:
我认为你的主要问题是你和你的朋友对“接口”这个词使用了两种不同的定义,所以你们在不同的方面都是对的。
您在日常意义上使用“接口”,即“与某物进行交互操作的定义方式”,例如“我的计算机和键盘之间的接口是 USB”或“真空吸尘器和墙壁之间的接口”权力就是一个出口。”从这个意义上说,是的,方法(甚至是具体的方法)是接口,因为它们定义了一种与对象进行互操作的方式。这并不是说这不适用于软件——它是术语应用程序编程接口 (API) 中使用的“接口”的含义。
您的朋友正在使用更具体的面向对象编程术语意义上的“接口”,即“类可以选择以保证其支持的单独定义的一组操作”。在这里,“接口”的定义特征是它没有自己的实现。一个类应该通过提供接口定义的方法的实现来支持接口。由于 C++ 在这个意义上没有明确的接口概念,因此等价的构造是一个只有纯虚函数(又名抽象数据类型)的类。
另一方面,“抽象”是关于很多事情的,而且你们都是对的。一般意义上的抽象意味着能够专注于更高层次的概念,而不是低层次的细节。封装是一种抽象,因为它的目的是隐藏类方法的实现细节;实现可以在不改变类定义的情况下改变。纯虚函数(OO 术语意义上的“接口”)是另一种抽象类型,因为如果使用得当,它们不仅可以隐藏实现,还可以隐藏真正的底层对象类型;只要两种类型都实现相同的接口,所使用的类型就可以改变。
【讨论】:
相同的术语可以用于不同的事物,这就是这里发生的事情。
C++ 中的“Abstract”是指根本没有实现的方法(不能用抽象成员实例化对象。)
“抽象”只是“建模”的概念。建模通过忽略一些细节使复杂的东西看起来更简单。在编程中,您希望将操作和概念分解为组件,并为每个组件抽象出不影响当前组件操作的外部组件的详细信息。
编程“接口”是一种实现“抽象”的方式。您不会看到组件的所有源代码和内部操作,而只会看到与您使用对象的方式相关的操作。 C++ 中的“接口”是通过将类上的所有方法标记为“抽象”(也称为“纯虚拟”)来实现的。这是通过在方法声明之后但分号之前放置“= 0”来完成的。该方法必须标记为“虚拟”才能合法。
换句话说,一个抽象的C++类是一个至少有一个纯虚方法的类,而一个接口是在C++中通过使所有的成员函数都成为纯虚来实现的。
封装是一个模糊的术语,但对我来说,它意味着一种实现抽象的技术。这意味着“信息隐藏”。您隐藏了对象如何执行其“合同”的内部细节。契约是通过接口表达的,在我看来这是一种更强大的抽象形式。任何具有受保护或私有成员的 C++ 类都使用封装,但仅实现纯虚方法的类正在描述契约,承诺提供某些服务,您无需了解它们的实现方式或同一对象可能的其他服务实施。
同一个对象可以填充多个合约,并且通过公开多个不相交的接口,它不会强制客户端了解该对象的所有辅助功能。例如,一个对象可能能够告诉您银行账户余额,它也可能能够被序列化/反序列化到数据库。你可以只拥有一个类,所有这些操作都作为成员函数公开。我更喜欢定义两个接口,“IDatabaseSerializable”和“IBankAccount”,并将适当的操作放在适当的接口中,并从我的实现类中的两个接口派生。那么只关心银行余额的客户会看到尽可能少的额外信息,而数据库只会看到它关心的操作。
【讨论】:
虚拟成员函数(不一定是纯的)与继承相结合是在 C++ 中表达接口概念的一种方式,即接口和方法是正交概念。
C++ 中接口的另一种方法是使用泛型代码(即模板),您通常不希望有任何具体类型 - 相反,您希望该类型具有具有特定语义的某些成员函数 - 如果您没有会出现编译错误。
有些人称其为概念,而您则说一种建模某个概念的类型。虽然 C++ 中没有正式支持概念,但像 Boost.ConceptCheck 这样的库在提供替代品方面做得很好。
【讨论】:
抽象意味着有人可以在不了解实现细节的情况下使用您的代码。使这件事变得复杂的是,在一个上下文中作为实现细节的东西可能不在其他上下文中。抽象数据类型是不能实例化的数据类型,只描述其子类型的属性。抽象类就是抽象数据类型和类。
接口可以由一组成员函数隐式定义(尽管要成为有用的抽象,这些函数至少应该是虚拟的,以便接口有多个可能的实现)。它也可以显式定义为仅具有纯虚函数(在 C++ 中)或 interface(在 Java、C# 和 D 中)的类。
抽象是指您不必必须了解某事物的实现细节。封装是指您无法了解它们。例如,从 OO 角度来看设计良好但不费心将其成员变量设置为 private 的类仍然是一个有用的抽象,但它没有被封装。
【讨论】: