软件构造 | 期末查缺补漏

news2024/10/6 6:01:29

软件构造 | 期末查缺补漏

总体观

在这里插入图片描述

软件构造的三维度八度图是由软件工程师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;
    }

    @Override
    public 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.coloradmin.cn/o/1866618.html

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

相关文章

文件批量重命名001到100 最简单的数字序号递增的改名技巧

文件批量重命名001到100 最简单的数字序号递增的改名方法。最近看到很多人都在找怎么批量修改文件名称&#xff0c;还要按固定的ID需要递增&#xff0c;这个办法用F2或者右键改名是不能做到的。 这时候我们可以通过一个专业的文件批量重命名软件来批量处理这些文档。 芝麻文件…

A-8 项目开源 qt1.0

A-8 2024/6/26 项目开源 由于大家有相关的需求&#xff0c;就创建一个项目来放置相关的代码和项目 欢迎交流&#xff0c;QQ&#xff1a;963385291 介绍 利用opencascade和vulkanscene实现stp模型的查看器打算公布好几个版本的代码放在不同的分支下&#xff0c;用qt实现&am…

如何获取特定 HIVE 库的元数据信息如其所有分区表和所有分区

如何获取特定 HIVE 库的元数据信息如其所有分区表和所有分区 1. 问题背景 有时我们需要获取特定 HIVE 库下所有分区表&#xff0c;或者所有分区表的所有分区&#xff0c;以便执行进一步的操作&#xff0c;比如通过 使用 HIVE 命令 MSCK REPAIR TABLE table_name sync partiti…

Redis实战—基于setnx的分布式锁与Redisson

本博客为个人学习笔记&#xff0c;学习网站与详细见&#xff1a;黑马程序员Redis入门到实战 P56 - P63 目录 分布式锁介绍 基于SETNX的分布式锁 SETNX锁代码实现 修改业务代码 SETNX锁误删问题 SETNX锁原子性问题 Lua脚本 编写脚本 代码优化 总结 Redisson 前言…

基于盲信号处理的人声分离

1.问题描述 在实际生活中&#xff0c;存在一种基本现象称为“鸡尾酒效应”&#xff0c;该效应指即使在非常嘈杂的环境中&#xff0c;人依然可以从噪声中提取出自己所感兴趣的声音。 在实际应用中&#xff0c;我们可能需要对混合的声音进行分离&#xff0c;此时已知的只有混合…

springcloud第4季 springcloud-alibaba之openfegin+sentinel整合案例

一 介绍说明 1.1 说明 1.1.1 消费者8081 1.1.2 openfegin接口 1.1.3 提供者9091 9091微服务满足&#xff1a; 1 openfegin 配置fallback逻辑&#xff0c;作为统一fallback服务降级处理。 2.sentinel访问触发了自定义的限流配置&#xff0c;在注解sentinelResource里面配置…

DVWA 靶场 SQL Injection 通关解析

前言 DVWA代表Damn Vulnerable Web Application&#xff0c;是一个用于学习和练习Web应用程序漏洞的开源漏洞应用程序。它被设计成一个易于安装和配置的漏洞应用程序&#xff0c;旨在帮助安全专业人员和爱好者了解和熟悉不同类型的Web应用程序漏洞。 DVWA提供了一系列的漏洞场…

数学建模--Matlab求解线性规划问题两种类型实际应用

1.约束条件的符号一致 &#xff08;1&#xff09;约束条件的符号一致的意思就是指的是这个约束条件里面的&#xff0c;像这个下面的实例里面的三个约束条件&#xff0c;都是小于号&#xff0c;这个我称之为约束条件符号一致&#xff1b; &#xff08;2&#xff09;下面的就是上…

关于linux的图形界面

关于linux的图形界面 1. 概述1.1 X1.2 DM&#xff08;显示管理器/登录管理器&#xff09;1.3 WM&#xff08;窗口管理器&#xff09;1.4 GUI Toolkits1.5 Desktop Environment1.6 基本架构 2. 安装桌面2.1 Centos安装桌面2.2 Ubuntu安装桌面&#xff08;未实践&#xff09; 3. …

批量打造怀旧风情:视频批量剪辑将现代视频打造成怀旧经典老视频效果

在繁忙的现代生活中&#xff0c;我们时常怀念那些旧时光&#xff0c;那些充满岁月痕迹的老电影片段。它们不仅记录了一个时代的风貌&#xff0c;更承载了无数人的情感与记忆。你是否想过&#xff0c;将现代的视频素材打造成这种怀旧经典的老视频效果&#xff0c;让每一帧都充满…

Python将Word文档转换为图片(JPG、PNG、SVG等常见格式)

将Word文档以图片形式导出&#xff0c;既能方便信息的分享&#xff0c;也能保护数据安全&#xff0c;避免被二次编辑。文本将介绍如何使用 Spire.Doc for Python 库在Python程序中实现Word到图片的批量转换。 目录 Python 将Word文档转换为JPG、JPEG、PNG、BMP等图片格式 Py…

基于信息论的高动态范围图像评价算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于信息论的高动态范围图像评价算法matlab仿真&#xff0c;利用一种自然图像的概率模型对图像的熵与成像动态范围之间的关系进行了数值模拟,得到了具有普遍意义上…

YOLOv10改进 | 卷积模块 | 将Conv替换为轻量化的GSConv【轻量又涨点】

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 专栏目录&#xff1a;《YOLOv8改进有效…

Docker搭建ELK

docker安装ElasticSearch 创建网络 #这里先创建一个网络&#xff1a;因为我们还需要部署kibana容器、logstash容器&#xff0c;需要让这些容器互联。 docker network create elk-net#查看网络 docker network ls下载ES镜像 #搜索镜像 docker search elasticsearch #下载镜像…

使用ChatGPT提升编程效率:程序员的最佳实践分享

在这个信息技术飞速发展的时代&#xff0c;编程已经成为了越来越多人的必备技能。无论你是初学者&#xff0c;还是经验丰富的开发者&#xff0c;都可能会遇到编程中的各种问题和挑战。幸运的是&#xff0c;AI 技术的进步让我们有了新的解决工具——ChatGPT。作为一名科技博客博…

OAuth2.0 三方登录(Google登录)

一、OAuth2.0流程 &#xff08;A&#xff09;客户端向从资源所有者请求授权。&#xff08;B&#xff09;客户端收到授权许可&#xff0c;资源所有者给客户端颁发授权许可&#xff08;比如授权码code&#xff09;&#xff08;C&#xff09;客户端与授权服务器进行身份认证并出示…

基于SpringBoot的学生综合测评系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot框架 工具&#xff1a;MyEclipse、Tomcat 系统展示 首页 系统首页&#xff0c;提供综合…

吴恩达机器学习作业ex5:正则化线性回归和偏差VS方差(Python实现)详细注释

文章目录 1.正则化线性回归1.1 可视化数据集1.2 正则化线性回归成本函数1.3 正则化线性回归梯度1.4 拟合线性回归 2 偏差-方差2.1 学习曲线 3.多项式回归3.1 学习多项式回归3.2 正则化参数的调整3.3 使用交叉验证集选择 λ3.4 计算测试集误差 1.正则化线性回归 在练习的前半部…

RT-Thread 实时系统介绍

介绍 RT-Thread 是一款开源的实时操作系统&#xff0c;主要面向物联网设备。它支持多种芯片架构&#xff0c;具有安全、低功耗、智能、可伸缩的特性。RT-Thread 拥有超过16年的技术积累&#xff0c;广泛应用于各行业&#xff0c;装机量达数十亿台。它提供了包括设备虚拟文件系…

Python学习打卡:day15

day15 笔记来源于&#xff1a;黑马程序员python教程&#xff0c;8天python从入门到精通&#xff0c;学python看这套就够了 目录 day15110、数据分析案例步骤 1 —— 文件读取data_define_108.pyfile_define_108.py 111、数据分析案例步骤二——数据计算112、数据分析案例步骤…