我在最后几天读了很多关于“接口程序”和“控制反转”的内容。主要是在Java语言的上下文中。我的问题是它是否也是C ++开发中的常见做法。有什么好处?有什么缺点?
是否值得申请小型项目(如15-20班)?
答案 0 :(得分:7)
是的,这是相当普遍的,但不是你所期望的形式。
在Java中,接口是形式化和显式的,对接口的编程意味着实现特定的interface
。
在C ++中,同样也是这样做的(虽然使用抽象基类而不是接口),但在C ++中使用另一种常见方式是使用模板,其中接口是隐式的。
例如,标准库算法都使用迭代器“接口”,除了在代码中没有定义过这样的接口。这是一个惯例,仅此而已。
需要有效的迭代器来公开某些功能,因此,任何公开此功能的类型都是迭代器。但它不必像在Java中那样实现某种假设的IIterator
接口。
用户代码中也很常见。您经常编写代码来接受模板参数,该参数可能是工作的任何内容。您通过使用类型隐式定义接口:您需要的任何内容都成为此隐式接口的一部分,类型必须满足该接口才能使用。
接口从未在代码中形式化,但您仍在使用它并对其进行编程。
答案 1 :(得分:6)
您所说的原则通常适用于任何OO语言。这里的基本原则是“松耦合”。依赖于另一个类(包含它的实例并在其上调用方法作为其自身工作的一部分)的类实际上仅依赖于依赖项提供的一组功能。如果类定义了对它所依赖的具体类的引用,然后您想要将该类替换为另一个类,则不仅需要开发新类,还要更改依赖类以依赖于新类型。这通常很糟糕,因为如果你的类依赖于许多其他类,你必须在多个地方更改代码,要求你测试涉及这些对象的所有用例,以确保你没有破坏以前工作的功能。 / p>
接口旨在消除这种情况,允许基于您知道类将实现的常见,强制执行的一组方法,可以交替使用与祖先无关的多个类。如果您依赖于某个类,而不依赖于某个类,那么实现该接口的任何类都将满足该依赖关系。这允许你编写一个新类来替换旧类,而没有使用它的类知道差异。您需要修改的只是创建填充依赖项的类的具体实现的代码。
这是一个窘境;当然,你的类Depender可以说它需要一个IDoSomething而不是一个DoerClass,但如果Depender知道如何创建一个DoerClass用作IDoSomething,你还没有获得任何东西;如果你想用BetterDoer替换DoerClass,你仍然必须改变Depender的代码。解决方案是赋予类向第三方(创建者)提供依赖关系实例的责任。为此选择的类取决于上下文。如果一个类自然同时具有Depender和DoerClass,那么将它们放在一起是显而易见的选择。当您有一个具有两个辅助依赖项的类时,通常会出现这种情况,并且一个依赖项也需要另一个依赖项。其他时候你可以创建一个Factory,它可以为调用者提供一个特定对象的实例,最好连接所有依赖项。
如果您有多个相互依赖的类或多个级别的依赖项,您可以考虑使用IoC框架。 IoC容器属于工厂,因为存储库属于DAO;他们知道如何为你提供一个需要一个或多个依赖关系的任何类的完全水合的实例,比如Repository可以从数据库中的数据生成任何完全水合的域对象。它通过被告知在某种情况下应该使用什么具体类来填充依赖项来实现这一点,并且当被要求提供类时,它将实例化该类,提供所需依赖项的实例(以及依赖项的依赖项)。这可以允许A类依赖于B的模式,这取决于C,但A不能知道C.IoC框架知道所有三个,并将实例化一个B,给它一个新的C,然后给一个新的B甲
答案 2 :(得分:4)
绝对!封装是OOP理念的重要组成部分。通过将实现与类的接口分开,您的代码变得更加通用。例如,如果我有一个'Vector'类,我想将内部表示从x和y对更改为长度和方向(假设它是为了提高效率),那么只需更改一些处理实现比从每个依赖于类实现的类搜索100个源文件更容易。
是的,小项目也可以从中受益。当您有多个类执行相同的操作(比如渲染)但以不同的方式(可能针对不同的平台)时,此概念也很有用。通过为它们提供所有相同的接口(或者在C ++中,从同一个基类派生它们),任何对象都可以通过简单的替换在它们之间切换。
答案 3 :(得分:1)
我认为术语可能存在一些混淆,因为“接口”不是C ++语言定义的术语。在您的问题的上下文中,您显然是指一个抽象类规范,可以通过一个或多个具体类来实现。
我不会说这很常见,但这也不常见 - 也许介于两者之间?微软的COM建立在这个概念之上。
有关如何完成的更多信息,请参阅此问题:How do you declare an interface in C++?
答案 4 :(得分:1)
C ++中的接口比Java中的接口更多。简单的答案是是,这是一种常见的做法。是否应该在您的小项目中遵循它取决于具体情况。判断每个班级,而不是整个项目。作为一般规则,如果没有破坏,请不要修复它。
也就是说,在C ++中,您有两种接口:支持运行时多态性的接口和支持编译时多态性的接口。运行时多态性与您在Java中非常相似。编译时多态性来自模板的使用。
运行时多态性的优点是它通常会产生一个小的二进制文件,并且它使编译器更容易在编译时生成有意义的错误消息。在不利方面,它也会导致二进制文件略慢,因为调用需要多一个解除引用。
编译时多态性的优点是通常您的源更小,并且调用的运行时优化到最快。另一方面,因为编译器需要做更多的工作,编译时往往会变慢。通常编译时间变得非常慢,因为模板通常在头文件中定义,因此对于依赖于它们的每个编译单元一次又一次地重新编译。
答案 5 :(得分:1)
我认为这些概念远远超出了OO,但同样适用于大多数(如果不是全部)可能在C ++中使用的范例,包括通用编程。
答案 6 :(得分:0)
好吧,任何使用任何编写库的语言的人都会使用“program to interfaces”。
库用户不希望库接口一直在变化。想象一下,如果你必须重写你的程序,因为C库作者决定重新定义“printf”。或者,如果Java库决定重新定义toString的接口呢?
所以是的,“程序到接口”在C ++中使用。
答案 7 :(得分:0)
接口越来越受Java欢迎,目前使用C#,在接口面向对象编程已经存在之前,有史以来最完整的语言(在我看来)C ++并没有在接口的基础上成长。创建接口的主要需求是解决必须具有多重继承的问题(c ++允许多重继承)。
在C ++中,定义接口(但可以通过纯抽象类模拟它们)不是扩展实践。另外,通常的做法是定义一个包含声明(.H)和其他包含实现(.CPP)的文件,这可能与纯抽象clasess一起用作创建现代OO语言接口的灵感。
答案 8 :(得分:0)
看看标准模板库的设计。例如msdn channel9有一些很好的视频教程,它们解释了STL的设计(和MSFT实现)。
http://channel9.msdn.com/Shows/Going+Deep/C9-Lectures-Introduction-to-STL-with-Stephan-T-Lavavej
答案 9 :(得分:0)
对于“程序接口”照常,取决于。 如果使用运行时多态,则缺点是执行速度和产生更多类。 如果对接口使用编译时多态,则缺点是编译速度和复杂性(涉及编译错误,调试等)。
两者都具有更好的可维护性的巨大优势。这通过封装实现,其产生更好的可测试性和更强的关注点分离。适应这种思维方式可以将问题缩小到其他组件接口所包含的较小范围。
这种思维方式在任何编程语言中都被认为是一种很好的实践,即使您没有明确地编写接口代码。 (另见SOLID原则)。
控制反转是C ++的另一个野兽,因为没有广泛接受的标准框架。你应该这样做,但必须自己管理它。这里最好的建议:远离静态并使用“程序到接口”,你应该适合小型项目。