C++抽象编程——接口(3)——接口设计的重点

编程困难的原因之一是程序反映了底层应用程序的复杂性。 只要计算机用于解决日益复杂的问题,编程过程也将变得越来越复杂。
编写一个程序去解决一个很大或困难的问题,迫使你管理一个惊人的程序的复杂程度。不仅仅有算法设计,还有特殊情况要考虑,以及是否满足用户要求,最后注意无数的细节才能正确。为了使编程易于管理,我们必须尽可能地减少编程过程的复杂性和功能的复杂性。库(library)的设计使得编程复杂性降低,但是具有更高的细节水平。一个函数使得它的访问者能够访问一组一起执行某个操作步骤的函数。
要设计一个有效的接口,必须平衡几个标准。一般来说,我们开发的接口应该满足这样的特点:

  • 统一性。单个接口应该使用一个清晰的统一主题来定义一致的抽象。如果一个函数不符合该主题,则应在单独的接口中定义。(比如,你想设计一个出口数学计算公式的接口,但是这里有一个函数是用于怎么处理字符串,这个时候它应该在单独的接口中定义,因为它们不属于一个主题)
  • 简洁性。在底层实现本身就很复杂的情况下,接口出口的函数必须寻求从客户端的使用上隐藏复杂性。 (就是出口的函数要好用)
  • 充实性。当客户端使用抽象时,接口必须提供足够的功能来满足他们的需求。如果接口缺少一些关键操作,客户端可能决定放弃它并开发自己的更强大的抽象。(即我们出口的函数应该提供足够多的函数来应付可能出现的其他情况)
  • 通用性。 精心设计的接口应该足够灵活,以满足许多不同客户的需求。为某一个客户而制定的接口并不像在许多不同情况下使用的那样有用。
  • 稳定性。接口中定义的函数应该继续具有完全相同的结构和效果,即使底层实现发生改变。

下面就针对这5个方面来详细探讨一下.

主题统一的重要性

Unity gives strength.
—Aesop, The Bundle of Sticks, ~600 BCE
精心设计的接口的核心特征是它提供了统一和一致的抽象。这部分标准意味着我们应该选择库内,以便它们反映出一个连贯的主题。因此,< cmath >库导出数学函数,< iostream >库导出cin,cout和cerr流以及执行输入和输出的运算符,“error.h” 接口导出报告功能 错误。这些库导出的每个接口条目都符合该接口的目的。当然我们不会期望在< string >库中找到sqrt,因为它更适合于< cmath >库的框架。

简洁性与信息隐藏

Embrace simplicity.
—Lao-tzu, The Way of Lao-tzu, ~550 BCE
因为使用接口的主要目标是减少编程过程的复杂性,所以把设计接口时的简单性作为一个理想的标准是有道理的。一般来说,接口应该尽可能简单易用,尽管底层实现可能会执行非常复杂的操作,但客户端应该能够以简单的方式思考这些操作。
在一定程度上,接口充当特定的库抽象的参考指南。当我们想知道如何使用error函数时,我们可以查阅error.h接口,了解如何执行此操作。该接口精确地包含了作为用户需要知道的信息。对于客户而言,获取的信息可能会太少,因为额外的细节可能会使接口更难理解。通常,接口的真正价值不在于它所揭示的信息,而在于隐藏的信息。
当我们设计接口时,应尽可能避免客户端尽可能多的复杂的实现细节。因此,最好不要将界面主要看作是客户端和实现之间的通信渠道,而是将它们分隔开来。就像下面的图一样:

像在希腊神话中将恋人Pyramus和Thisbe分开的墙壁,代表一个接口的墙壁包含一个允许客户端和实现通信的连接(就是中间的缝隙)。但是,隔离墙的主要目的是让双方分开。因为我们将它设想为位于库中所代表的抽象边界,所以接口有时被称为抽象边界(abstraction boundary)。理想情况下,实现库所涉及的所有复杂性都在墙壁的实现(implementation)方面。 如果库的复杂性能远离客户端,该接口就是成功的。 这种将细节限于实现域的做法我们称为信息隐藏(information hiding)。
信息隐藏的原理对接口设计有重要的实践意义。在编写接口时,即使在注释中,也应该确保不会显示实现的细节。特别是如果在同一时间编写接口和实现,你可能会有在接口中描述你在实现中使用的所有聪明的想法我们应该抵制这种诱惑。因为接口是为客户的利益而编写的,应仅包含客户需要知道的内容。
同样,我们在接口中设计函数,使其功能尽可能简单。如果您可以减少参数数量或找到一种方法来消除特殊情况,客户端将更容易了解如何使用这些功能。而且,优点是限制接口导出的功能总数,使得客户端不会在大量函数中迷失,反而无法全面了解接口。

满足客户端的需求

Everything should be as simple as possible, but no simpler.
—attributed to Albert Einstein
在许多情况下,接口的客户端不仅涉及特定功能是否可用,而且还涉及其实现的效率。例如,如果我们正在开发一种用于空中交通管制的系统,并且需要调用一个库接口的,这些函数必须快速返回正确答案。迟到的答案可能与错误的答案一样糟糕。
在大多数情况下,效率是实现而不是接口的关键。即使如此,在设计接口本身时,经常会考虑策略的可行性。假设,例如,你面对两种设计的选择。如果您确定其中一个将更容易实现高效性那么选择该设计。

一般性的好处

完美适应特定客户需求的接口可能对其他用户无益。良好的库抽象服务于许多不同客户的需求。要做到这一点,一般来说足以解决各种各样的问题,而不仅限于一个高度具体的目的。通过选择提供灵活性的设计,可以创建广泛使用的接口。

稳定的数值

People change and forget to tell each other. Too bad— causes so many mistakes.
—Lillian Hellman, Toys in the Attic, 1959
接口具有另一个属性,使它们对编程至关重要:它们在长时间内往往是稳定的。稳定的接口可以通过建立明确的责任界限来大大简化维护大型编程系统的问题。只要接口不改变,实现者和客户端都可以自由地在抽象边界的一边进行更改。
例如,假设你是数学库的实现者。在你的工作过程中,你会发现一个聪明的新算法,用于计算sqrt函数,减少计算平方根所需的一半时间。这是如果可以向客户说,有一个新的sqrt实现方式,就像以前一样,只有更快,他们可能会很高兴。但是另一方面,如果你说这个功能的名称已经改变了,或者说它的使用涉及到一些新的限制,那么你的客户就会有理由感到厌烦。因为要使用“改进”的平方根执行,他们将被迫改变以前写的程序。
改变程序是一个耗时的,容易出错的活动,许多客户将乐意放弃额外的效率,以方便他们独自完成他们的计划。
然而,接口仅在程序维护保持稳定时简化了程序维护任务。随着新算法的发现或应用程序的要求发生变化,程序会频繁更改。但是,在这种演进过程中,接口必须尽可能保持一致。在精心设计的系统中,改变实现的细节是一个简单的过程。我们应该使这种变化所涉及的复杂性被局限在抽象边界的实现方面。另一方面,改变接口往往会产生一个全局的变动,需要改变每一个依赖于它的程序。因此,接口变化应该很少进行,只有在客户的积极参与下才能进行。

不过,一些界面的改变让我们更加方便。例如,将一个全新的函数添加到接口通常是一个相对简单的过程,因为没有其他的进程已经依赖该功能。改变接口,使现有程序将继续运行而不修改,称为扩展接口(extending the interface)。 如果发现需要在界面的整个生命周期内进行更改,通常最好通过扩展进行更改。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值