软件构造教程(预习或复习自取三)

 

第一章:软件开发概述

第二章:模块化软件构造

第三章:面向对象的软件构造

3.1抽象与封装

        3.1.1模块产生与合成
  • 函数簇包含了对核心数据的产生、变更和使用的操作函数。函数簇与其之外的其他函数通常不能产生、变更函数簇内的核心数据,仅仅使用。
  • 从模块化理解,一个函数簇实现了一组围绕核心数据的功能,具备通信内聚和功能内聚,属于强内聚。
  • 函数簇与其他函数、函数簇、模块的连接方式主要是数据的传递及对理解函数簇数据的函数的调用。
  • 局部化,如函数和复合语句的内部变量,是实现模块化的一种重要机制。
  • 函数调用可以视为模块组合的一种机制。通过函数调用把函数联系起来,构成更大规模的程序。
  • 数据结构,如数组或C的结构体为组成更大、更复杂的数据提供了构建具有层次结构的组织方式。
  • 单纯的数据结构不含对数据的操作,也不能保护其中的数据元素。
  • 可以在数据结构和数据类型的基础上实现模块化机制,这就需要抽象与封装。
        3.1.2抽象与封装
 抽象
  • 是指对于一个过程或一件事物的某些细节有目的地隐藏,以便把其他方面、细节或结构表达得更加清楚。
  • 抽象是从众多的事物中抽取出共同的、本质性的特征,而舍弃其非本质的特征。
  • 抽象的目的是通过把相关的属性和其他不相关的属性分开,分离关注点。
  • 抽象是处理复杂问题的一个手段。
  • 抽象是分离对象特性、限制对它们在当前环境关注的个机制。
程序设计有两种抽象,分别是过程抽象(函数抽象)和数据抽象。
  • 过程抽象是在使用一个函数或方法时知道它干什么,而不知道它是如何完成的。
  • 数据抽象是将一个数据类型的特性(值及其运算)与其实现分离。
  • 在软件构造中,我们需要一种机制来实现抽象,呈现关注点,隐藏其他细节。

封装有两个含义∶
  • 把描述一个事物的性质和行为结合在一起成为构件,对外形成该事物的一个界限,封装使构件能够集中而完整地对应并描述具体的事物,体现了事物的相对独立性;
  • 信息隐蔽,即外界不能直接存取构件的内部信息(属性)及隐藏起来的内部操作,外界也不必知道操作的内部实现细节。
信息隐藏强制封装。
 
在软件开发中封装具有如下优势:
  • 构件外部只能通过外部可用的操作来访问内部数据和操作,降低了构件间的耦合度。
  • 构件内部修改对外部的影响变小,减少了修改引起整个程序范围的“波动效应”。
  • 更容易快速开发正确的程序,一旦程序员们确定了构件间的交互,每个人都可以独立地开发和测试分配的构件。改善通用性和维护性。

严格的封装也会带来诸如编程麻烦、执行效率的问题。有些语言不强调严格的封装和信息隐藏,而采取可见性控制来解决这些问题。
例如,C++和Java语言通过定义对象的属性和操作的可见性,规定了其他对象对其属性和操作的可访问性另外,一个对象也可以通过把相应的可见性指定为受保护的或私有的,而提供仅局限于特定对象的属性和操作。


        3.1.3抽象数据类型
具体数据类型∶
  • 数据类型是一个值的集合和定义在值集上的一组操作的总称。
  • 明显或隐含地规定了数据的取值范围、存储方式及其运算。
  • 数据类型可分为:原子类型和结构类型。
  • 程序设计语言都提供了基本数据类型。每个数据都属于某种数据类型。
  • 对于复杂的信息,编程处理时仅仅使用基本数据类型是不够的。比如,可以把“学生”的取值集合与规定的一组操作定义成一种数据类型。
 抽象数据类型(Abstract Data Type,ADT)
  • 一个数据模型及定义在该模型上的一组操作。
  • 定义一个ADT 时,必须给出它的名字及各操作的名称,并且规定这些函数的参数性质。
  • 一旦定义了一个ADT及具体实现,程序设计中就可以像使用基本数据类型那样,十分方便地使用ADT。
  • 例,学生ADT,包含各种属性及取值范围;还有一组操作,如按照不同属性查询学生的信息、更改一些信息。

  • 一个程序员定义的ADT 要通过高级编程语言中已有的数据类型和数据结构来实现。
  • 利用基本的数据结构可以构造一些复杂的数据结构,如栈、队列、树、图等,对数据集进行抽象而不必让使用者(程序员)关心实际数据的存储细节(ADT的数据结构)。
  • ADT的定义与其实现分离,使其用户只需关注如何使用,而不是它的实现细节。
  • 这种分离要求与ADT 的交互使用接口(定义的操作集)。
  • 通过封装实现细节,要求通过接口访问ADT,使得程序员能使用抽象进行工作,专注ADT 提供的功能,而不必理解这些功能的实现。

面向对象为实现ADT 提供了良好的机制。类:ADT的一种实现方式。加上继承、聚合、多态等特性,不仅提高了软件开发效率,还能提高软件产品的质量,使得面向对象的程序设计获得普遍应用。
面向对象语言提供了良好的编程机制,但语言本身并不能保证开发出高质量程序,因而还需要学习和运用设计、编码、测试等软件构造的基本原理,更好地运用面向对象语言的特性来构造出高质量的程序。
 

3.2认识面向对象(理解类、对象和多态的概念,理解类之间的几种关系)

3.2.1设计类
  • 用面向对象开发软件时,我们把系统模块化成类,它们封装了属性及其操作。
  • 决定一个类知道什么、做什么,就是抽象出一个类。
  • 在设计一个类如何做事情时,就是把它们封装起来。
  • 良好设计的类限制访问它的属性及其操作,实质上就是隐藏了信息及其处理。
     
抽象是设计类的基本方法。
  • 抽象是在某个东西周围画上一个白盒子的动作:识别出它做什么、不做什么。
  • 抽象是对某个东西定义接口的动作。
  • 抽象告诉我们存储学生的姓名、学号,也能让学生选课。但是,抽象不告诉我们是如何做到这些的。
封装则处理如何将这些特性模块化。
  • 封装解决的是如何划分一个系统的功能的设计问题。
  • 封装是在某个东西周围画上一个黑盒子的动作:它明确某事能完成,但是不告诉是怎样做到的。换句话说,封装对类的使用者隐藏了实现细节。
  • 为了使应用程序容易维护,要限制访问类的数据和操作。
     
模块化原则
  • 紧内聚、松耦合仍然适用于评价面向对象程序。
  • 包括类的内聚、方法的内聚;不同类之间的耦合、同一个类不同对象之间的耦合及同一个类内函数之间的耦合。
  • 类的模块化准则要求一个类应当是完整的、原始的、充分的
     
类的设计
  • 要尽量使其所有的操作都是原始的,每个操作仅提供简单、良好定义的行为。
  • 根据松散耦合的原则,也倾向于分离操作、减少它们之间的沟通。
平衡矛盾
  • 把复杂的行为集中在一个方法中,简化了接口,但其实现复杂了;
  • 反之,方法行为和实现简单了,但方法多了,接口复杂了。
     
3.2.2设计操作

通常在面向对象开发中,把类的方法作为整体来设计,这是因为所有这些方法的合作构成了抽象的全部协议。

设计时考虑以下建议。
  • 复用:这个行为在更多的环境中更有意义吗?
  • 复杂:实现这个行为有多难?
  • 适应:这个行为与其置身的类有多少关系?
  • 实现知识:实现这个行为要依赖于类的内部细节吗?
 多态
  • 多态是类型理论的一个概念,一个名字可以表示多个不同类的实例,只要它们具有某个共同的超类而且相关。
  • 所以,被这个名字表示的任何对象都能以不同的方式对一组某些相同的操作做出响应。
  • 由于多态,一个操作就能在层次结构中的所有类以不同方式实现。这样,子类就能扩展超类的能力或者覆盖超类的操作。
  • 很多类具有相同协议时,多态最有用。如果没有多态,程序中会出现大量的if 或switch语句。

3.2.3分类
  1. 类的使用会增加更多的类,类需要划分。
  2. 分类是对整理知识的一致手段。就是试图把具有共同结构或表现出共同行为的事情分为一组。
  3. 识别类和对象是面向对象开发的一个挑战,识别包含发现和发明。
  • 通过发现,我们认识到构成问题域词语的关键抽象和机制!
  • 通过发明,设计出一般化的抽象和机制,说明对象是如何协作的。
  • 发现和发明都是分类问题,其核心就是发现问题的共性。
类之间的关系
  • 在类之间建立关系的原因,首先是一种类关系会指出某种共享。
  • 其次,一种类关系会指出某种语义联系。

 接口与实现
  • Meyer认为程序设计本质上是契约:一个较大问题的功能通过把它们分包到不同元素的设计,而分解成若干较小的问题。
  • 类的接口提供了外部视角,重在抽象,同时隐藏了它的结构和组成。接口主要包括声明。
  • 类的实现是其内部视角,包含其行为,主要由所有定义在类接口操作的实现组成。
类的接口可以进一步分成4类。
  • 公共的:对所有用户可访问的声明;
  • 保护的∶仅允许类本身及其子类访问的声明;
  • 私有的:仅允许类本身访问的声明;
  • 包:仅允许和类在用一个包的声明。

接口是一些面向对象语言的基本元素,如Java语言的接口Interface,必须有具体的类才能实现接口定义的操作。
 
 

3.3面向对象的设计

设计符号

软件设计语言或符号,如可视化图形设计符号。在面向对象设计中,普遍采用的包括描述程序静态结构的类图、描述程序动态行为的交互图。它们掩藏了类中方法的实现细节,突出了类的组成和类之间的关系,简洁清晰地表达设计意图和内容。
复杂的算法、数据结构等操作的实现,仍然使用代码和伪代码补充说明。

统一建模语言UML(大佬详讲UML)

UMLUnified Model Language的缩写,中文是统一建模语言,是由一整套图表组成的标准化建模语言。
UML 不仅能使软件建模可视化,还有助于分析、评估和验证软件设计,支持从UML自动产生部分代码,指导产生测试用例。

设计原则

单一职责原则、里氏代换原则、依赖倒转原则:

单一职责原则( Single ResponsibilityPrinciple,SRP)

  • 软件设计中的单一职责原则( Single ResponsibilityPrinciple,SRP)是最容易理解和运用的。
  • 单一职能原则实际上是内聚原则在面向对象方法中的具体表现,含义是就单个类而言,应该只有一个引起它变化的原因。

里氏代换原则(Liskov Substitution Principle ,LSP )
设计类层次结构的基本原则,它要求子类型必须能够替换其基类型。使用里氏代换原则的两个要点如下。

  • 子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。
  • 尽量把父类设计为抽象类或接口,让子类继承父类或实现父接口,并实现在父类中声明的所有方法。

依赖倒转原则

  • 子类必须实现抽象父类定义的抽象方法才能生成对象,即低层的子类依赖高层的、抽象的父类。
  • 这种设计思想运用了面向对象技术中的依赖倒转原则(Dependency Inversion Principle,DIP ),它转换了依赖关系:高层模块不依赖于低层模块,两者都依赖于抽象。抽象不依赖于实现,实现依赖于抽象。
  • 换言之,就是高层模块定义接口,低层模块负责实现。“上转型对象”是面向对象方法中实现依赖倒转原则的有效技术。

3.4调试的基本技术

使用调试器调试程序的基本模式如下∶

  • 首先设置(若干)断点,启动运行调试器;
  • 遇到一个断点后停下,单步调试一段代码,可以检查、改变断点的程序状态和行为。
  • 然后恢复,让调试器继续执行程序,直到遇到下一个断点或执行到程序的结束。
  • 调试可在任何时刻终止。
     
        3.4.1单步调试源程序

 1.设置断点:
在要检查的语句上设置断点,程序执行到断点处会暂停。通过窗口观察此刻变量的值、检查程序运行情况;可以输入改变变量或表达式的值,然后让程序继续运行。
Eclipse中在代码行左边的页边空白处双击设置断点。

2.单步调试
利用单步命令,从断点处开始一次处理一条语句。
变量值有助于程序员仔细观察程序的执行流程、了解程序变量值的变化、调查可疑代码。
在debug 视图中有三种方式:

  • 执行视图中的图标。
  • 右击出现的选择(含图标)
  • 快捷键。

3 .l临时断点
有时需要在显示的代码中临时设置有期限的断点以便细致观察程序。
在Eclipse中,突出显示源码窗口中要设置临时断点的代码行,然后右击并选择Run to Line。

        3.4.2检查/更改变量的值

        3.4.3设置监视点观察变量
  • 监视点(watchpoint)结合了断点和变量检查的概念。每当指定变量的值发生变化时,都暂停程序的运行。
  • 监视点对局部变量的用途一般没有对作用域更宽的变量的用途大,因为一旦变量超出作用域(如函数结束),在局部变量上设置的监视点就会被取消。main()中的局部变量例外,因为其中的变量要等到程序执行结束时才会被释放。

        3.4.4 上下移动调用栈
  • 与函数关联的运行时信息存储在称为帧的内存区域中。帧中包含了函数局部变量的值、形参,以及调用该函数的位置。
  • 系统为每个调用函数创建一个帧,并将其放在一个运行栈上;运行栈最上面的帧表示当前正在运行的函数,当函数退出时,这个帧就退出运行栈并释放所占的内存。
  • 运用调试器可以观察运行栈,追踪函数之间的调用关系和变量值的来源、变化等信息。Eclipse中,运行栈在debug透视图本身连续可见。
     

3.5软件自动化测试

  • 合适的测试工具和构建工具,可以提高构建效率,是现代软件开发的基础。
  • JUnit已经成为现代软件开发方法和工具的核心组成。它是Java语言的测试框架,是包含测试用例、测试执行、测试记录等一组Java类和接口。
  • JUnit由于简单、实用、易用,特别适合自动化的单元测试和回归测试。
3.5.1初识Junit

使用JUnit进行测试的基本步骤:

  1. 建立测试类,命名规则︰待测类名+Test,如
  2. BinaryOperationTest。在该类的前面用@RunWith 指定汉试运行器,默认JUnit4。
  3. 在用@Before注解的setUp()中为测试做必要准备(测试装置fixture ).
  4. 为待测类的成员方法/函数编写测试方法,命名规则:test+待测方法,以@Test注解这个待测方法,其中务必包含测试断言。
  5. 运行测试,查看运行结果,更改代码。(5)增加方法或修改代码时,重复(3、4)。

JUnit4使用注解Annotation简化了测试编程。下面是用到的部分JUnit注解。

  • @Test:定义之后的方法是测试方法,否则后面的方法不是测试代码。测试方法必须是public void,即公共、无返回值的;可以抛出异常。
  • Betore :使用以土群的力i法仕母个测试万法孙行乙刖都要执行一次。主要用于一些独立于用例之间的准备工作,比如创建一个对象,或者打开文件,或者连接数据库。
  • @After:使用了该注解的方法在每个测试方法执行之后要执行一次,与@Before对应,但不是必需的。
  • @Runwith :执行测试的运行器。放在测试类名之前,用来确定测试类是怎么运行的。不指定则使用默认Runner来运行测试代码。
  • @Ignore :该注解标记的测试方法在测试中会被忽略。

3.5.2编写Junit测试代码
1.基本测试——建立测试
  • JUnit把任何用@Test注解的方法当成一个测试用例。测试方法或测试函数可以随意命名,它们既没有参数,也没有返回值。
  • 测试方法代码的核心是调用待测程序得到实际的运行结果,用断言assertEquals比较待测程序的运行结果与预期结果是否相等。
  • 通过记录断言的真假,统计测试运行的成功与失败次数。

 

1.基本测试——运行测试
  • 测试进度条上面显示了测试总数与成功运行的测试数Runs ( 3/3)、测试代码错误数Errors ( 0)及测试失败数Failures ( 3),同时显示了测试运行的时间(秒数)。
  • Error指的是测试程序没有考虑到的情况,在断言之前测试程序就因为某种错误引发例外而终止。
  • Failure指的是在测试用例中给出的预期结果与实际的运行结果不同所导致的测试失败。
     

 2.追踪失败的测试
  • 如果测试没有通过,Failures显示失败的测试数及测试函数。
  • 在Failure Trace下面可以查看错误,
  • 还可以深入探究失败的原因∶选择一个失败的测试,右击,选择“Compare Result”,可以查看预期结果与实际结果的比较。

第四章:数据处理的软件构造

第五章:用户交互的软件构造

第六章:软件重构与交付

第七章:GUI软件构造

第八章:应用数据库

第九章:基于复用的软件构造

  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
语法分析器是编译器的核心组件之一,它的主要任务是将输入的源代码转换成抽象语法树,以便于后续的语义分析和代码生成。为了构造一个语法分析器,需要完成以下预习任务: 1. 学习上下文无关文法(CFG) 上下文无关文法是描述编程语言语法的一种形式化语言。了解上下文无关文法的概念和性质,以及如何使用上下文无关文法来描述编程语言的语法,是构造语法分析器的基础。 2. 熟悉自顶向下语法分析算法 自顶向下语法分析算法是一种从语法的起始符号开始,逐步推导出输入符号串的过程。常见的自顶向下语法分析算法有LL算法和递归下降算法。了解这些算法的原理和实现方式,可以帮助构造语法分析器。 3. 了解预测分析表 预测分析表是一种用于自顶向下语法分析的数据结构,它将文法的非终结符和终结符组合在一起,形成一个表格。了解预测分析表的构造方法和使用方式,可以帮助构造语法分析器。 4. 掌握ANTLR工具 ANTLR是一种用于构造语法分析器的工具,它可以根据输入的语法规则自动生成语法分析器。掌握ANTLR工具的使用方法,可以大大提高语法分析器的开发效率。 5. 理解语法错误处理 在构造语法分析器时,需要考虑如何处理语法错误。了解语法错误的种类和如何进行错误处理,可以帮助构造健壮的语法分析器。 总之,构造语法分析器需要深入了解上下文无关文法、自顶向下语法分析算法、预测分析表、ANTLR工具和语法错误处理等知识,并且需要具备一定的编程能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值