Skip to content

Latest commit

 

History

History
36 lines (29 loc) · 4.84 KB

04_contract.md

File metadata and controls

36 lines (29 loc) · 4.84 KB

《《《 返回首页
《《《 上一节

契约

在阅读软件设计时,你很可能会遇到合同一词,通常没有任何附带的解释。事实上,软件工程给这个术语的含义非常接近人们通常理解合同的含义。在日常使用中,合同定 义了双方可以期望的彼此之间 - 在某些交易中彼此承担的义务。如果合同规定了供应商向客户提供的服务,供应商的义务是显而易见的。但客户也可能有义务 - 除了支付 义务外 - 并且未能履行义务也会自动将供应商从其义务中解脱出来。例如,航空公司的运输条件 - 无论如何我们可以承担的机票类别 - 使他们免于承担未能按时出行的 乘客的义务。这允许航空公司计划他们的服务,假设所有乘客都是准时的;他们不需要招揽额外的工作来接纳尚未履行合同一方的客户。

合同在软件中的使用方式与此相同。如果一种方法的合同规定了其论点(即,客户必须履行的义务)的先决条件,则该方法只有在满足这些先决条件时才需要返回其合同结 果。例如,二分查找(参见第 17.1.4 节)是一种在有序列表中查找密钥的快速算法,如果将其应用于无序列表,则会失败。因此,Collections.binarySearch 的 契约可以说,“如果列表未排序,结果未定义”,二进制搜索的实现者可以自由编写代码,给定无序列表,返回随机结果,抛出异常或甚至进入无限循环。实际上,这种情况 在核心 API 的契约中相对较少,因为它们不是限制输入有效性,而是主要考虑前提条件中的错误状态,并指定方法在输入错误时必须抛出的异常。这种设计可能适用于 一般类库,如集合框架,它将广泛用于各种各样的情况和具有广泛不同能力的程序员。您应该避免使用不太常用的库,因为它会不必要地限制供应商的灵活性。原则上, 客户需要知道的一切就是如何保持合同的一面;如果它没有这样做,所有的投注都关闭,应该不需要说明供应商会做什么。

Java 中的良好习惯是将代码编写到接口而不是特定的实现中,以便在选择实现时提供最大的灵活性。为了达到这个目的,它对实现的行为意味着什么?例如,如果您的 客户端代码使用 List 接口的方法,并且在运行时执行该工作的对象实际上是一个 ArrayList ,则您需要知道您对 Array 列表行为的假设也适用于 ArrayLists。所以实现接口的类必须履行接口合同条款规定的所有义务。当然,这些义务的较弱形式已经由编制者强加于人;声明实现接口的类必须提供与接口中的声明 匹配的具体方法定义。合同通过指定这些方法的行为来进一步说明这一点。

集合框架以不同寻常的方式分离界面和实施义务。某些 API 方法返回具有受限功能的集合,例如,您可以从 Map 中获取的一组键可以删除但不添加元素(请参阅第 16 章)。其他人可以不添加或删除元素(例如,由 Arrays.asList 返回的列表视图),也可以是完全只读的,例如已包装在不可修改的包装中的集合(请参见第 17.3.2 节)。框架中的各种行为没有出现接口数量的爆炸,设计师将 Collection 接口(以及 IteratorListIterator 接口中)的修改方法标记为可选 操作。如果客户端尝试使用集合未实现的可选操作修改集合,则该方法必须抛出 UnsupportedOperationException。此方法的优点是,框架接口的结构非常简单,每 个库中的优点是每个 Java 程序员必须学习。缺点是客户端程序员不能再依赖于接口的合同,但必须知道正在使用哪个实现,并且为此咨询合同。这是非常严重的,你 可能永远不会在你自己的设计中以这种方式颠覆接口。

班级合同明确了客户在使用时可以依靠什么,通常包括性能保证。 然而,要充分了解某个类的性能特征,您可能需要了解一些有关它所使用算法的细节。 在本书的这一部 分,虽然我们主要关注合同,以及作为客户程序员如何使用它们,但我们也会从平台类中提供一些可能有用的实现细节。 这在确定实现之间可能很有用,但请记住它不稳 定; 虽然合约具有约束力,但使用它们的主要优点之一是它们允许实现在更好的算法被发现时改变,或者随着硬件改进改变它们的相对优点。 当然,如果你正在使用另一 个实现,比如 GNU Classpath,那么不受合同约束的算法细节可能完全不同。

《《《 下一节
《《《 返回首页