软件构造 | 期末查缺补漏

软件构造 | 期末查缺补漏

总体观

在这里插入图片描述

软件构造的三维度八度图是由软件工程师Steve McConnell提出的概念,用于描述软件构建过程中的三个关键维度和八个要素。这些维度和要素可以帮助软件开发团队全面考虑软件构建的方方面面,从而提高软件质量和开发效率。

下面是软件构造的三维度八度图的三个关键维度和八个要素:

三个关键维度:

  1. 作用域(Scope):定义软件项目的功能和目标,包括需求分析、功能规格等。
  2. 资源(Resources):涉及到开发软件所需的人员、时间、技术、工具等资源。
  3. 质量(Quality):关注软件的质量特性,如可靠性、性能、安全性等。

八个要素:

  1. 工具(Tools):用于支持软件开发、测试和管理的工具。
  2. 文档(Documentation):包括需求文档、设计文档、用户手册等。
  3. 设计(Design):软件架构和设计的方案。
  4. 构建(Construction):软件编程和编码的实施。
  5. 调试(Debugging):识别和修复软件中的错误和缺陷。
  6. 测试(Testing):对软件进行单元测试、集成测试、系统测试等各个阶段的测试。
  7. 部署(Deployment):将软件部署到目标环境中,并确保正常运行。
  8. 管理(Management):包括项目管理、团队管理、进度跟踪等管理活动。

这个三维度八度图提醒软件开发团队在软件构建过程中不仅要关注代码编写,还要考虑到需求管理、资源分配、质量保证等各个方面。通过全面考虑这些要素,软件开发团队可以更好地规划、执行和控制软件项目,达到项目的目标。

版本控制和git

22题2

在这里插入图片描述

在执行了上述git指令后,HEAD指向了master分支。让我们逐步分析这个过程:

  1. git checkout -b test: 创建并切换到test分支。也就是说,现在程序员从master分支创建了一个新的分支test,并且切换到了test分支上工作。此时,HEAD指向test分支上的最新commit节点。

  2. 程序员在test分支上修改了代码,使用git add *将修改的文件添加到暂存区,然后使用git commit -m "first"进行提交,生成了一个新的commit节点,此时test分支上有了第一个commit。

  3. git checkout master: 切换回master分支。这个指令让HEAD指向了master分支上的最新commit节点,也就是说程序员又回到了在master分支上工作。

  4. 程序员在master分支上再次修改了代码,添加到暂存区并提交了第二次commit,生成了另一个新的commit节点。

  5. 最后,程序员执行了git merge test,将test分支与master分支进行合并。在合并完成后,master分支上会包含test分支上的更改,并形成一个新的合并commit。此时,HEAD仍然指向master分支上最新的合并commit节点。

综上所述,在执行完上述一系列的操作后,HEAD指向了master分支的最新合并commit节点。

关键名词

  1. Version Control System VCS 版本控制系统
  2. Repository仓库
  3. git remote获得当前配置的所有远程仓库
  4. git remote add [shortname] [url]添加一个新的远程仓库

Git commit

在Git中,一个commit可以指向一个或多个父级。具体分析如下:

  1. 一个commit指向一个父亲:这是Git中最常见的情况,通常发生在正常的代码提交历史中。每个提交(commit)除了保存了文件快照外,还包含了指向其直接父提交(parent commit)的引用。这种情况下,每个提交都有一个唯一的父提交,形成一个线性的提交历史。

  2. 一个commit指向多个父亲:这种情况在Git中称为合并提交(merge commit)。合并提交发生在分支合并时,当将一个分支的更改合并到另一个分支时,会创建一个合并提交,该提交指向合并的两个父提交。合并提交会保留合并之前两个分支的所有更改,并将它们合并在一起。这种情况下,一个提交有多个父提交,形成一个分支合并的历史记录。

总的来说,一个commit指向一个父亲是线性的提交历史,而一个commit指向多个父亲是分支合并的历史记录。Git通过这种灵活的方式来管理代码提交历史,使得多人协作、分支管理等操作变得更加高效和灵活。

Git repository

中的三个主要部分是 working directory(工作目录)、staging area(暂存区)和 git directory(仓库目录)。

  1. Working Directory(工作目录):工作目录是包含实际项目文件的地方,即我们修改、添加、删除文件的目录。当我们在工作目录中修改文件时,Git会跟踪这些变化,并在适当的时候将这些变化提交到仓库。

  2. Staging Area(暂存区):暂存区是一个缓冲区,用于存放我们已经修改过的文件,并准备将这些修改提交到仓库。在暂存区中,我们可以选择性地将一部分修改添加到下一次提交中,而不是一次性提交所有变化。

  3. Git Directory(仓库目录):仓库目录是Git的核心部分,包含了项目的完整提交历史、分支、标签等信息。在仓库目录中,Git会保存项目的所有版本记录、元数据和配置信息。

各部分之间的关系:

  • 当我们对工作目录中的文件进行修改后,Git会跟踪这些变化。
  • 我们使用 git add 命令将工作目录中的修改添加到暂存区,Git会将这些修改暂时存放在暂存区中。
  • 最后,我们使用 git commit 命令将暂存区中的修改提交到仓库目录中,形成一个新的提交。这样,文件的修改就会保存在Git仓库的提交历史中。

在整个工作流程中,工作目录、暂存区和仓库目录之间的交互关系使得Git能够追踪文件的修改历史、管理版本控制,并允许我们灵活地控制要提交的修改内容。

Object Graph对象图

在Git中,每个提交(commit)都被表示为一个节点(node)在对象图(object graph)中。对象图是Git中用来存储数据对象的数据结构,其中每个数据对象(如提交、文件、目录等)被表示为一个独立的对象,而节点则代表这些对象之间的关系。

每个提交节点包含了提交的元数据(作者、时间、提交消息等)以及指向各自数据对象的哈希值。通过这种方式,Git能够构建出一个有向无环图(DAG),其中每个节点代表一个提交,每个提交指向它的父提交或多个父提交(在分支合并时)。

通过对象图,Git能够记录项目的完整提交历史,追踪文件的修改,同时保留每个提交所包含的所有信息。这使得Git非常强大和灵活,可以轻松处理多个分支、合并以及版本控制等操作。

因此,节点在对象图中代表了Git中的提交,它们之间的连接形成了一个有向图,反映了项目的提交历史及其关系。理解节点在对象图中的作用有助于更深入地理解Git是如何管理和跟踪项目的变化的。

数据类型

  • 22题4

在这里插入图片描述

为什么错:

B. 一个 immutable 的 ADT,也可能会包含 mutator 方法

这说法不恰当,因为一个不可变(immutable)的 ADT(抽象数据类型)应该是没有 mutator 方法的。不可变对象的设计原则之一就是不提供能够修改对象状态的方法,一旦对象被创建后,其状态就不能再被改变。因此,一个完全不可变的 ADT 不应该包含 mutator 方法。

为什么对:

A. 客户端构造得到的 Set/Map/List 对象并不一定都是 mutable 的

  • 这一说法正确,因为Java中的集合类(Set、Map、List等)通常提供了不同类型的实现,有些实现是可变的(mutable),即可以修改其中的元素;而有些实现是不可变的(immutable),一旦创建就不能修改。因此,客户端构造得到的集合对象并不一定都是可变的。

C. 声明变量时,在前面加上 final 关键字,可保证该变量是 immutable 的以提高安全性

D. 使用 immutable 类型可以降低程序蕴含 bug 的风险,但可能导致时空性能变差

  • 这说法正确,使用不可变类型可以降低程序中因为状态变化而引入的bug风险,因为不可变对象的状态在创建后不会改变,更容易理解和维护。但由于不可变对象在发生变化时需要创建新的对象,可能会导致时空性能变差,尤其是在频繁修改大型对象时。

综上所述,选项B中的说法不恰当,其他选项A、C、D中的说法是正确的。

  • 21第5题

在这里插入图片描述

在Java中,dynamic checking通常指的是在运行时对代码执行的检查,比如数组越界、空指针异常、类型转换异常等。这些检查是由JVM(Java虚拟机)在运行时自动执行的。

现在,我们来看这些选项:

A.

final StringBuilder sb = new StringBuilder("abc");  
sb.append("d");  
sb = new StringBuilder("e");

这段代码是合法的。final修饰的sb引用不能指向另一个对象,但这里只是修改了sb引用的值(这实际上是可以的,因为final修饰的是引用而不是引用指向的对象),并且调用了append方法。这段代码在运行时不会抛出任何异常,因此能够通过dynamic checking。

B.

List<String> strs = new ArrayList<>();  
strs.add("HIT");  
for(String s : strs)  if(s.startsWith("HIT"))   strs.remove(s);

这段代码在运行时会抛出ConcurrentModificationException,因为在增强for循环中直接修改了集合strs。这种修改在迭代过程中是不允许的,因为它会破坏迭代器的内部状态。因此,这段代码不能通过dynamic checking。

C.

List<String> list = new ArrayList<>();  
List<String> copy = Collections.unmodifiableList(list);  
copy.add("c");

这段代码在运行时会抛出UnsupportedOperationException,因为Collections.unmodifiableList返回的列表是不允许修改的。尝试调用其add方法会抛出异常。因此,这段代码不能通过dynamic checking。

D.

String text = "hello" + new String("world");   
text = text + false + 1 + 0.1;

这段代码是合法的,并且会正确执行。Java会自动将false10.1转换为字符串,并与前面的字符串进行连接。这段代码在运行时不会抛出任何异常,因此能够通过dynamic checking。

综上所述,能够通过dynamic checking的是AD。但如果题目只要求选择一个最恰当的答案,那么D选项更加“纯粹”,因为它没有修改任何集合或尝试执行不允许的操作,而A选项尽管最终是合法的,但包含了修改final引用的操作(尽管这在Java中是允许的)。所以,如果必须选择一个,则D是最恰当的答案。

  • 21第6题

在这里插入图片描述

Mutability and Immutability

String是一种不变对象

String s = "a";
s = s.concat("b");

需要重新为s赋值,让s指向一个新的引用。否则s仍然是“a”

StringBuilder是一种可变对象

StringBuilder sb = new StringBuilder("a");
sb.append("b");

不必为sb重新改引用。

Defensive Copying防御式拷贝

假设我们有一个Person类,其中包含一个List类型的属性List<String> phoneNumbers,表示电话号码列表。如果我们希望在Person类的方法中避免原始的phoneNumbers列表被修改,就可以使用防御式拷贝来处理。

示例代码如下所示:

import java.util.ArrayList;
import java.util.List;public class Person {private List<String> phoneNumbers;public Person(List<String> phoneNumbers) {// 使用防御式拷贝,复制传入的phoneNumbers列表this.phoneNumbers = new ArrayList<>(phoneNumbers);}// 获取电话号码列表public List<String> getPhoneNumbers() {// 返回一个新的列表,而不是直接返回原始phoneNumbers列表return new ArrayList<>(phoneNumbers);}
}

在上面的示例中,Person类中的构造方法和getPhoneNumbers方法都使用防御式拷贝来处理phoneNumbers属性。在构造方法中,传入的phoneNumbers列表会被复制一份,而不是直接引用;在getPhoneNumbers方法中,同样也会返回一个新的列表的副本,而不是直接返回原始列表。

通过这种方式,无论在何时调用Person类的方法,都不会影响原始的电话号码列表,因为每次都是操作副本而不是直接操作原始数据。这样可以确保数据对象的不可变性,避免数据被意外修改或破坏。

snapshot digrams

  1. 基本数据类型:一个单箭头
  2. 对象数据类型:单箭头指向椭圆
  3. 不可变对象:单箭头指向双线椭圆
  4. 不可变引用,双线箭头。

ADT

软件构造 | Designing Specification-CSDN博客

软件构造 | Abstract Data Type (ADT)-CSDN博客

  • 22第6题

在这里插入图片描述

答案:A

  • 22第7题

在这里插入图片描述

C. 如果 R 中的两个值被 AF 映射为 A 中的同一个值,那么这两个值所代表的对象是等价的

这个说法不完全准确。在抽象数据类型(ADT)的上下文中,如果两个具体的表示(R 中的值)通过抽象函数(AF)映射到相同的抽象值(A 中的值),这通常意味着这两个具体的表示在抽象级别上是不可区分的。然而,说它们“是等价的”可能过于宽泛,因为“等价”在不同的上下文中可能有不同的含义。在 ADT 的上下文中,我们通常只是说它们在抽象级别上是“不可区分的”。

  • 22第8题

在这里插入图片描述

  • 22第9题

在这里插入图片描述

在这个问题中,我们有两个类 X 和 Y,其中 Y 是 X 的子类。这两个类都有一个名为 hit 的方法,但它们的参数类型不同:X 类有一个 hit(X x) 方法,而 Y 类有两个 hit 方法:一个是从 X 类继承的 hit(X x),另一个是重载的 hit(Y y)

现在,我们来看客户端代码中的对象和方法调用:

  • X a = new X(); 声明了一个 X 类型的变量 a,并初始化为 X 的一个实例。
  • Y b = new Y(); 声明了一个 Y 类型的变量 b,并初始化为 Y 的一个实例。
  • X c = new Y(); 声明了一个 X 类型的变量 c,但实际上存储了一个 Y 的实例(这是向上转型,因为 Y 是 X 的子类)。

现在,我们分析每个选项:

A. a.hit(b)

  • a 是 X 的一个实例,它有一个 hit(X x) 方法。因为 b 是 Y 的一个实例,而 Y 是 X 的子类,所以 Y 的实例可以赋值给 X 类型的参数。因此,这个调用是合法的。

B. b.hit(a)

  • b 是 Y 的一个实例,因此它有两个 hit 方法:一个是 hit(X x)(从 X 继承的),另一个是 hit(Y y)(Y 类自己定义的)。因为 a 是 X 的一个实例,不是 Y 的实例,所以这里调用的是 hit(X x) 方法(即继承自 X 的那个),这也是合法的。

C. c.hit(b)

  • c 是 X 类型的一个变量,但实际上存储了一个 Y 的实例(向上转型)。由于我们在 Java 中只能使用 c 引用 Y 对象中的 X 类定义的方法(多态性),所以我们不能使用 hit(Y y) 方法(因为它是 Y 类特有的,且没有在 X 类中定义)。但是,c 可以调用 hit(X x) 方法,因为 b 是 Y 的实例,而 Y 是 X 的子类,所以调用 c.hit(b) 实际上是调用了 Y 类中的 hit(X x) 方法(由于多态性),这也是合法的。

因此,所有的选项 A、B、C 都是可以通过编译器的静态检查的。答案是 D(以上都对)。

  • 21第10题

在这里插入图片描述

最恰当的选项是 C

关于 hashCode() 的说法,我们可以逐一分析选项:

A. 错误。即使你不打算将 ADT 对象放入集合(如 HashSet, HashMap 等)中使用,重写 hashCode() 仍然可能是有意义的。例如,如果你打算在自定义的数据结构中实现某种基于哈希的查找或存储机制,那么重写 hashCode() 会很有帮助。但如果你完全确定你的 ADT 对象永远不会基于哈希进行比较或存储,那么这个选项在技术上可能是正确的,但从一般编程实践的角度来看,它并不是“最恰当”的。

B. 错误。根据 Java 的 hashCode() 契约,两个不相等的对象可以返回相同的哈希码(虽然理想的哈希函数应该尽可能少地产生碰撞)。equals() 方法用于确定两个对象是否相等,而 hashCode() 方法仅用于生成一个整数,该整数用于哈希数据结构中的桶定位。

C. 正确。根据 Java 的 hashCode()equals() 契约,如果子类没有引入新的属性来影响相等性,并且没有重写 equals() 方法,那么它通常也不需要重写 hashCode() 方法。这是因为父类的 hashCode() 实现已经考虑了所有影响相等性的属性。

D. 部分正确,但过于绝对。虽然通常建议在计算哈希码时考虑对象的所有重要属性,但这也取决于你的具体需求和设计决策。例如,如果某些属性对相等性没有影响,或者你知道某些属性在哈希数据结构中的分布是均匀的,那么你可能不需要在 hashCode() 中包含这些属性。

关键名词解释

  1. Representation invariant(RI):表示不变式,是对数据表示的约束条件或规则,确保数据表示的合法性和一致性。RI定义了在特定数据表示下,数据的哪些属性必须满足特定条件,不得处于无效状态。RI在类的内部应该被维护和遵守,以确保程序的正确性和可靠性。
  2. Representation(rep):字段:表示数据类型的内部数据结构或状态,是ADT(Abstract Data Type)中的具体实现。rep描述了如何组织和存储数据的具体细节,包括数据的存储形式、数据结构和数据之间的关系。rep的设计应符合RI的约束,以保证数据表示的有效性。
  3. Abstraction Function(AF):表示抽象函数,是ADT的抽象视角,描述了数据表示和抽象之间的映射关系。AF定义了如何将具体的数据表示映射到抽象的概念或视角上,帮助理解数据的含义和行为。
  4. spec规约:传入参数,返回参数,条件,功能要求。
  5. Creators:构造器是用于创建新对象并初始化其状态的特殊类型的方法。在Java中,构造器的主要作用是初始化类的新实例。
  6. Producers:用于基于现有对象生成新的对象。生产器方法通常不会改变原始对象的状态,而是返回一个新对象,继承或衍生自原始对象。
  7. Observers:观察器是一种类型的方法,用于查看对象的状态或属性,而不修改它们
  8. Mutators:变值器是一种方法类型,用于改变对象的状态或属性值

OOP

软件构造 | Object-Oriented Programming (OOP)-CSDN博客

  • 22 第10题

在这里插入图片描述

  • 22第11题

在这里插入图片描述

]答案是B。

让我们逐一分析这些选项:

A.这是正确的。在Java中,LinkedList<Object>是实现了List<Object>接口的类,因此我们可以说LinkedList<Object>List<Object>的子类型

B. 这是不正确的。在Java的泛型系统中,List<Integer>并不是List<Object>的子类型。这是因为Java的泛型是不变的(invariant),这意味着List<Integer>List<Object>是两个完全不相关的类型。你不能将一个List<Integer>的实例赋值给一个List<Object>的变量

C.这是正确的。在Java中,所有的类(包括List接口的实现)都是Object的子类。因此,可以将任何类的实例(包括IntegerList的实现)存储在Object[]数组中。

关键名词解释

  1. Interface :接口中只有方法的定义,没有实现;可以继承和扩展,可以有多个实现类。
  2. Enumerations:枚举
  3. Abstract Class:抽象类是包含抽象方法的类,用abstract关键字声明。抽象类不能被实例化,只能被用作父类。抽象类可以包含抽象方法和非抽象方法,非抽象方法可以有具体的实现
  4. Polymorphism多态
  5. Overriding:重写一个方法:使用@Override 编译器compiler会检查覆盖方法和被覆盖方法的签名是否完全一致。动态多态性。
  6. overloading重载,静态多态性,参数多态性。方法名相同但参数列表不同,根据调用的方法和传递的参数类型确定具体的方法调用。
  7. Generics:泛型编程是一种编程风格,其中数据类型和函数是根据待指定的类型编写的,随后在需要时根据参数提供的特定类型进行实例化。
  8. Subtyping Polymorphism子类型多态。包括接口多态。

Design Patterns

软件构造 | Design Patterns for Reuse and Maintainability-CSDN博客

  • 22第12题

在这里插入图片描述

答案C

在考虑如何在两个ADT(抽象数据类型)A和B之间建立委派(delegation)关系时,我们需要关注的是如何设计这种关系以便能够灵活地适应未来的变化。委派是一种设计模式,其中一个对象(在这里是A)将其部分职责委派给另一个对象(在这里是B)。

现在,我们来分析给出的选项:

A. 这个选项在A的创建时就确定了委派对象。如果未来需要更改委派对象,你将不得不重新创建A的实例,这限制了灵活性。

B. 这个选项在A的内部直接创建了一个B的子类型的实例,并且这个实例在A的生命周期内是不可变的(除非在A内部有额外的逻辑来改变它)。这同样限制了灵活性,因为你不能更改委派的对象,除非A的设计允许它这么做。

C. 这个选项允许你在A的实例被创建之后,通过调用setDelegation方法来更改委派的对象。这提供了最大的灵活性,因为你可以在任何时候更改委派的对象,而不需要重新创建A的实例。

D. 这个选项在每次调用hit()方法时都创建一个新的委派对象,这通常不是委派模式所期望的。委派模式通常意味着将职责委派给一个持久存在的对象,而不是每次需要时都创建一个新的对象。

  • 22 第13题

在这里插入图片描述

Factory Method(工厂方法)设计模式不是通过delegation机制实现的

  • 22第14题

在这里插入图片描述

最恰当的实现机制是 B

在考虑ADT(抽象数据类型)A如何允许客户端遍历其内部复杂数据结构时,我们需要选择一个既安全又易于使用的机制。以下是各个选项的分析:

A. 这个选项直接暴露了ADT的内部数据结构,可能导致表示泄露(representation exposure),即客户端可能直接修改内部数据结构,破坏ADT的封装性。

B. 这个选项是Java等语言中常见的遍历机制,它允许ADT提供一个迭代器(Iterator)对象,客户端通过这个迭代器来遍历ADT的内部数据结构,而不需要直接访问内部数据结构。这样既保证了ADT的封装性,又提供了遍历的灵活性。

C. 这个选项虽然提供了遍历的功能,但没有遵循标准的遍历接口(如Java的Iterable和Iterator),这可能导致与其他代码的不兼容,且如果其他ADT也需要类似的功能,就需要重复实现这些方法。

D. 这个选项过于严格,完全禁止了遍历操作,这在很多情况下是不现实的,因为遍历是ADT的常见需求。

  • 21第11题

在这里插入图片描述

不恰当的说法是 B

关于 inheritance(继承)和 delegation(委托)的说法,我们可以逐一分析这些选项:

A. 这是正确的。无论是继承还是委托,它们都是代码复用的重要手段。通过继承,子类可以复用父类的代码;通过委托,一个类可以将其部分功能委托给另一个类来处理。

B. 这个说法可能不太恰当。按照面向对象设计的原则,继承通常表示“是一种”的关系,即子类(B)是父类(A)的一种特殊形式。如果B只是A的一个具有少量额外功能的版本,那么使用继承可能不是最佳选择,因为这可能导致继承层次过深或违反里氏替换原则(Liskov Substitution Principle)。在这种情况下,组合(composition,一种形式的委托)可能是更好的选择。

C. 这是正确的。在委托中,类A通常包含一个类型为B的私有成员变量,这样A就可以通过该变量调用B的方法来复用B的功能。

D. 这个说法也是正确的。继承是在类定义时确定的,它定义了子类与父类之间的静态关系。而委托是在运行时通过对象之间的交互来实现的,因此它发生在对象层面。

  • 21第13

在这里插入图片描述

答案:A

关键名词

  1. delegation:委派

各类模式特点

  • Creational patterns ->Factory Method pattern:工厂方法,有一个中间类Factory专门用来生成对象。
Product p = new ConcreteTwo().makeObject();
  • Structural patterns -> Adapter适配器模式:适配器模式的关键在于它提供了一种机制,使得不兼容的接口能够协同工作,而不需要修改现有的类代码。这有助于提高代码的灵活性和可维护性。
// 目标接口
interface Target {void request();
}// 需要适配的类
class LegacySystem {public void doSomething() {System.out.println("Doing something with Legacy System");}
}// 对象适配器
class LegacySystemAdapter implements Target {private LegacySystem legacySystem;public LegacySystemAdapter(LegacySystem legacySystem) {this.legacySystem = legacySystem;}@Overridepublic void request() {// 使用组合的方式调用doSomething方法legacySystem.doSomething();}
}
  • Structural patterns -> Decorator
public abstract class CoffeeDecorator implements Coffee {  protected Coffee coffee;  public CoffeeDecorator(Coffee coffee) {  this.coffee = coffee;  }  @Override  public double getCost() {  return coffee.getCost();    }  @Override  public String getDescription() {  return coffee.getDescription();  }  
}// 装饰器
public class MilkDecorator extends CoffeeDecorator {  public MilkDecorator(Coffee coffee) {  super(coffee);  }  @Override  public double getCost() {  return super.getCost() + 0.5; // 假设加奶增加0.5元  }  @Override  public String getDescription() {  return super.getDescription() + ", Milk";  }  
} 
  • Behavioral patterns ->Strategy:有多种不同的算法来实现同一个任务,但需要client根据需要 动态切换算法.
public class PaymentClient {  private PaymentStrategy paymentStrategy;  public PaymentClient(PaymentStrategy strategy) {  this.paymentStrategy = strategy;  }  public void makePayment(int amount) {  paymentStrategy.pay(amount);  }  public static void main(String[] args) {  PaymentClient client = new PaymentClient(new CreditCardStrategy("John Doe", "1234567890123456", "123", "12/25"));  client.makePayment(100); // 100 paid with credit card  client = new PaymentClient(new PaypalStrategy("user@example.com", "password"));  client.makePayment(50);  // 50 paid using Paypal.  }  
}
  • Behavioral patterns ->Template Method设计模式是一种行为设计模式,它在一个方法中定义了一个算法的骨架,并允许子类为一个或多个步骤提供实现。模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。
public abstract class OrderProcessTemplate {public boolean isGift;public abstract void doSelect();public abstract void doPayment();public void giftWrap() {System.out.println("Gift wrap done.");
}public abstract void doDelivery();public final void processOrder() {doSelect();doPayment();if (isGift)giftWrap();doDelivery();}
}
  • Behavioral patterns ->Iterator客户端希望遍历被放入 容器/集合类的一组ADT对象.
// 聚合接口  
interface Aggregate {  Iterator createIterator();  
}  // 迭代器接口  
interface Iterator {  boolean hasNext();  Object next();  
}  // 具体聚合(例如一个列表)  
class MyList implements Aggregate {  private List<Object> elements = new ArrayList<>();  // 添加元素的方法  public void add(Object element) {  elements.add(element);  }  // 创建迭代器的方法  @Override  public Iterator createIterator() {  return new MyListIterator(this);  }  // 内部类:具体迭代器  private class MyListIterator implements Iterator {  private int currentIndex = 0;  private MyList list;  public MyListIterator(MyList list) {  this.list = list;  }  @Override  public boolean hasNext() {  return currentIndex < list.elements.size();  }  @Override  public Object next() {  if (!hasNext()) {  throw new NoSuchElementException();  }  return list.elements.get(currentIndex++);  }  }  
}  // 客户端代码  
public class Client {  public static void main(String[] args) {  MyList list = new MyList();  list.add("Element 1");  list.add("Element 2");  list.add("Element 3");  Iterator iterator = list.createIterator();  while (iterator.hasNext()) {  System.out.println(iterator.next());  }  }  
}
  • visitor
public interface Visitor {  void visit(NodeA nodeA);  void visit(NodeB nodeB);  
}  public interface Node {  void accept(Visitor visitor);  
}  

异常

  • 21第15题

在这里插入图片描述

关于给出的四个关于 assertexception 的做法,我们来逐一分析它们:

A. 对 Java 中的 error 和 unchecked 异常,编程时最好不要用 try/catch 的方式来捕获

这个做法通常是正确的。Error 是 Java 中表示系统级错误的类,比如 OutOfMemoryError,它们通常是无法恢复的,因此不建议用 try/catch 来捕获。而 unchecked 异常(运行时异常,如 NullPointerException)通常表示编程错误,也不应该通过 try/catch 来处理,除非你有特别的理由需要这样做。

B. 一个方法根据传入的磁盘路径读入一个文件,该方法内部首先应该检查文件是否存在,若不存在,则用 assert false 语句终止程序执行

这个做法是不恰当的。assert 语句在 Java 中主要用于在开发和测试期间进行调试,它用于检查一个条件是否为真。如果条件为假,并且启用了断言(默认情况下断言是禁用的),那么程序会抛出一个 AssertionError。但是,对于文件不存在这种业务逻辑上的错误,应该使用异常(比如 FileNotFoundException)来处理,而不是 assert

C. 在一个方法的最开始,最好应检查 pre-condition 是否满足,若不满足,用“抛出异常”的方式提示用户比用 assert 语句更合适

这个做法是正确的。当方法的 pre-condition(前置条件)不满足时,使用异常来通知调用者是更合适的选择,因为异常是 Java 中用于处理错误情况的机制。

D. 在一个方法的 return 语句之前(若无 return 则在方法最后),应检查 post-condition 是否满足,若不满足,用 assert 语句比用“抛出异常”的方式更合适

这个做法在某些情况下是合适的,特别是当 post-condition 的不满足表示了一个内部错误或不一致的状态,而不是一个可以由调用者处理的错误时。但是,这也取决于具体的情况和上下文。在某些情况下,即使 post-condition 不满足,也可能需要抛出异常来通知调用者。

综上所述,最不恰当的做法是 B,因为对于文件不存在这种业务逻辑上的错误,应该使用异常来处理,而不是 assert。所以正确答案是 B。

try -catch

当我们在Java中处理异常时,我们通常会遵循一套标准的模式来确保程序的健壮性和可维护性。以下是一个具体的例子,展示了如何在Java中使用try-catch-finally块来处理异常。

假设我们有一个从文件中读取数据的方法,并且这个方法可能会遇到FileNotFoundExceptionIOException这两种类型的异常。

import java.io.BufferedReader;  
import java.io.FileReader;  
import java.io.IOException;  public class ExceptionHandlingExample {  public static void main(String[] args) {  String filePath = "example.txt"; // 假设这是我们要读取的文件路径  String fileContent = readFile(filePath);  System.out.println("文件内容: " + fileContent);  }  public static String readFile(String filePath) {  StringBuilder contentBuilder = new StringBuilder();  try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {  String line;  while ((line = reader.readLine()) != null) {  contentBuilder.append(line).append("\n");  }  return contentBuilder.toString();  } catch (FileNotFoundException e) {  // 处理文件未找到的情况  System.err.println("文件未找到: " + filePath);  e.printStackTrace();  return null; // 或者抛出一个更具体的自定义异常  } catch (IOException e) {  // 处理其他I/O错误  System.err.println("读取文件时发生I/O错误: " + filePath);  e.printStackTrace();  return null; // 或者抛出一个更具体的自定义异常  } finally {  // 无论是否发生异常,这里的代码都会被执行  // 在这个例子中,由于我们使用了try-with-resources语句,所以不需要显式关闭reader  // 如果这里没有使用try-with-resources,我们需要在这里显式调用reader.close()  System.out.println("文件读取完成(无论是否成功)");  }  }  
}
  1. try块:包含可能会抛出异常的代码。在这个例子中,我们使用BufferedReader从文件中读取内容。由于文件可能不存在或者其他I/O问题,这段代码可能会抛出FileNotFoundExceptionIOException
  2. catch块:用于捕获并处理try块中抛出的异常。在这个例子中,我们有两个catch块,一个用于处理FileNotFoundException,另一个用于处理IOException。在每个catch块中,我们都打印出错误消息,并调用e.printStackTrace()来打印异常的堆栈跟踪。然后,我们返回一个null值(或者可以抛出一个更具体的自定义异常)。
  3. finally块:无论try块中的代码是否抛出异常,finally块中的代码都会被执行。在这个例子中,我们打印出一条消息来表示文件读取已经完成(无论是否成功)。注意,由于我们使用了Java 7引入的try-with-resources语句,所以我们不需要在finally块中显式关闭BufferedReader。try-with-resources语句会自动关闭实现了AutoCloseableCloseable接口的资源。

这个例子展示了Java异常处理机制的基本用法,包括try-catch-finally块的使用、异常类型的捕获以及资源的自动管理。

throw

class MyException extends Exception {  public MyException(String message) {  super(message);  }  
}  public void doSomething() {  // ... 一些代码 ...  if (/* 某个条件 */) {  throw new MyException("发生了某种错误");  }  // ... 其他代码 ...  
}

显示的抛出异常。

如果某方法实现中包含了 throw new xxException(...) 的代码,则该方法的 spec(规范或接口)中一定会包含 throws xxException

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/35746.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

利用LinkedHashMap实现一个LRU缓存

一、什么是 LRU LRU是 Least Recently Used 的缩写&#xff0c;即最近最少使用&#xff0c;是一种常用的页面置换算法&#xff0c;选择最近最久未使用的页面予以淘汰。 简单的说就是&#xff0c;对于一组数据&#xff0c;例如&#xff1a;int[] a {1,2,3,4,5,6}&#xff0c;…

2024年6月26日 (周三) 叶子游戏新闻

老板键工具来唤去: 它可以为常用程序自定义快捷键&#xff0c;实现一键唤起、一键隐藏的 Windows 工具&#xff0c;并且支持窗口动态绑定快捷键&#xff08;无需设置自动实现&#xff09;。 土豆录屏: 免费、无录制时长限制、无水印的录屏软件 《Granblue Fantasy Versus: Risi…

PS教程29

图层蒙版 以案例来解释蒙版的作用 将这两张图片原框背景切换将图二的背景选中使用套索工具选中区域切换图一CtrlA全选CtrlC复制编辑-选择性粘贴-贴入即可贴入如果位置不对用移动工具进行调整 这就是图层蒙版 图层蒙版本质作用&#xff1a;是临时通道&#xff0c;支持黑白灰三种…

Linux开发讲课16--- 【内存管理】页表映射基础知识2

ARM32页表和Linux页表那些奇葩的地方 ARM32硬件页表中PGD页目录项PGD是从20位开始的&#xff0c;但是为何头文件定义是从21位开始&#xff1f; 历史原因&#xff1a;Linux最初是基于x86的体系结构设计的&#xff0c;因此Linux内核很多的头文件的定义都是基于x86的&#xff0c…

Java中Collection的成员及其特点

Collection集合 list集合系列 ArrarList集合 底层基于数组来实现 查询速度快&#xff08;根据索引查询数据&#xff09; 删除效率低&#xff08;可能需要把后面很多的数据往后移&#xff09; 添加效率…

软件构造 | Abstract Data Type (ADT)

软件构造 | Abstract Data Type (ADT) ​ 抽象数据类型与表示独立性&#xff1a;如何设计良好的抽象数据结构&#xff0c;通过封 装来避免客户端获取数据的内部表示&#xff08;即“表示泄露”&#xff09;&#xff0c;避免潜在 的bug——在client和implementer之间建立“防火…

鸿蒙开发Ability Kit(程序框架服务):【FA模型切换Stage模型指导】 配置文件差异

配置文件的差异 FA模型应用在[config.json文件]中描述应用的基本信息&#xff0c;一个应用工程中可以创建多个Module&#xff0c;每个Module中都有一份config.json文件。config.json由app、deviceConfig和module三部分组成&#xff0c;app标签用于配置应用级别的属性&#xff…

裸机与操做系统区别(RTOS)

声明&#xff1a;该系列笔记是参考韦东山老师的视频&#xff0c;链接放在最后&#xff01;&#xff01;&#xff01; rtos&#xff1a;这种系统只实现了内核功能&#xff0c;比较简单&#xff0c;在嵌入式开发中&#xff0c;某些情况下我们只需要多任务&#xff0c;而不需要文件…

前端项目结构介绍与Vue-cli(脚手架)环境搭建

传统的前端项目结构 一个项目中有许多html文件 每一个html文件都是相互独立的 如果需要在页面中导入一些外部依赖的组件(vue.js,elementUI),就需要在每一个html文件中引用都导入,十分的麻烦 而且这些外部组件都需要在其官网中自行下载,也增加了导入的繁琐程度 当今的前端项…

PMBOK® 第六版 实施整体变更控制

目录 读后感—PMBOK第六版 目录 对于变化的态度&#xff0c;个人引用两句加以阐释&#xff0c;即“流水不腐&#xff0c;户枢不蠹”与“不以规矩&#xff0c;不能成方圆”。这看似相互矛盾&#xff0c;实则仿若两条腿总是一前一后地行进。有一个典型的例子&#xff0c;“自由美…

基于IM948(Low-cost IMU+蓝牙)模块的高精度PDR(Pedestrian Dead Reckoning)定位系统 — 可以提供模块和配套代码

一、背景与意义 行人PDR定位系统中的PDR&#xff08;Pedestrian Dead Reckoning&#xff0c;即行人航位推算&#xff09;背景意义在于其提供了一种在GPS信号不可用或不可靠的环境下&#xff0c;对行人进行精确定位和导航的解决方案。以下是关于PDR背景意义的详细描述&#xff1…

Python代码打包成exe应用

目录 一、前期准备 二、Pyinstaller打包步骤 Pyinstaller参数详解 三、测试 Spec 文件相关命令 一、前期准备 &#xff08;1&#xff09;首先&#xff0c;我们需要确保你的代码可以在本地电脑上的pycharm正常运行成功。 &#xff08;2&#xff09;我们要先安装Pyinstalle…

AI智能体 | 扣子Coze 工作流中如何嵌入代码,看这一篇就够了

Coze的工作流中除了能嵌入大模型&#xff0c;插件&#xff0c;图像流&#xff0c;其他工作流外&#xff0c;还能嵌入代码。嵌入代码的好处是对一些复杂的返回结果进行二次处理。 Coze的代码支持js和python两种语言。这次用python来做演示介绍 在节点中选择代码 弹出对话框如下…

python-docx 设置页面边距、页眉页脚高度

本文目录 前言一、docx 页面边距在哪里二、对 <w:pgMar> 的详细说明1、上边距的说明2、右边距的说明3、下边距的说明4、左边距的说明5、页眉高度的说明6、页脚高度的说明三、设置 docx 页边距、页眉页脚高度1、完整代码2、代码执行效果图四、补充一些内容1、页面边距的两…

CCS的安装步骤

CCS的安装步骤 安装之前有几件重要的事情要做&#xff1a; 首先肯定是要下载安装包啦&#xff01;点击此处是跳到官网下载地址安装包不能处的路径中不能包含中文关闭病毒防护和防火墙&#xff0c;以及其他杀毒软件最后是在重启后进行安装 主要的步骤如下&#xff1a; 找到安…

电脑高手推荐:三款超实用软件,让你的电脑如虎添翼!

7Zip 7-Zip是一款免费且开源的文件压缩工具&#xff0c;支持多种文件格式&#xff0c;包括其自带的7z格式、ZIP、GZIP、BZIP2和TAR等。该软件由Igor Pavlov于1999年开发&#xff0c;具有高压缩比的特点。7-Zip不仅可以在Windows操作系统上使用&#xff0c;还可以在Unix-like的操…

【MySQL】数据库事务详解

文章目录 前言1. 事务的定义2. 事务的四个特性2.1 原子性2.2 一致性2.3 隔离性2.4 持久性 3. 事务的并发问题3.1 脏读3.2 不可重复读3.3 幻读3.4 更新丢失 4. 事务的隔离级别5. 事务的使用结语 前言 假设我们现在需要操作数据库进行转账&#xff0c;A 给 B 转账 100 块钱&…

高效修复机床导轨磨损,保障加工精度!

机床导轨是支承和引导运动构件沿着一定轨迹运动的传动装置&#xff0c;在机器设备中是个十分重要的部件&#xff0c;在机床中是常见的部件。机床的加工精度与导轨精度有直接的联系&#xff0c;且导轨一旦损坏&#xff0c;维修较复杂且困难。我们简单总结了以下几点对于机床导轨…

【会议征稿,IEEE出版】第三届机器人、人工智能与智能控制国际会议(RAIIC 2024,7月5-7)

第三届机器人、人工智能与智能控制国际会议&#xff08;RAIIC 2024&#xff09;将于2024年7月5-7日中国绵阳举行。 RAIIC 2024是汇聚业界和学术界的顶级论坛&#xff0c;会议将邀请国内外著名专家就以传播机器人、人工智能与智能控制领域的技术进步、研究成果和应用做专题报告…

网站推广如何做?这七个方法要知道

在出海独立站商家中&#xff0c;推广是必不可少的环节。在你完成网站的搭建&#xff0c;产品的上架&#xff0c;以及网站的运营和优化后&#xff0c;你就可以开始着手推广你的网站了。你的网站是承载你的品牌和产品的主要平台&#xff0c;因此&#xff0c;你需要根据你的品牌和…