本学期的高级软件工程可以总体而言可以分为五个部分,其中,第一部分介绍了软件工程中的常用工具如git,vscode,第二部分以一个Menu软件的开发过程为例,介绍了一个软件开发过程中体现的软件工程思想。第三、四、五部分分别介绍了需求分析于软件设计、软件科学概论、软件危机与软件过程。这三个部分的内容与传统的软件工程课程内容比较相似。而前两个部分的内容则是这门课程与其他软件工程课程有所不同,更偏向于使用常用的工具解决工作中的实际问题,相比其他课程的教学内容更注重代码与实践,突出软件工程的实际价值。对提高实操技能很有意义,比较具有课程特色。
在复习的过程中,应当围绕课堂教授的内容来开展。掌握好前两个部分的课堂案例以及后面几个部分展示的软件工程概念于思想。以下内容是对本学期课程内容的复习:
第一部分:工欲善其事 必先利其器
一、VSCode
Visual Studio Code(以下简称vscode)是一个轻量且强大的代码编辑器,支持Windows,OS X和Linux。内置JavaScript、TypeScript和Node.js支持,而且拥有丰富的插件生态系统,可通过安装插件来支持C++、C#、Python、PHP等其他语言。
vscode的优点:
•简洁而聚焦的产品定位,贯穿始终
VS Code专注于开发者“最常用”的功能:编辑器+代码理解+版本控制+远程开发+调试
•进程隔离的插件模型
VS Code 不信任任何人,把插件们放到单独的进程里,插件进程怎么折腾也无法干扰主进程代码的执行,主程序的稳定性得到了保障
•UI 渲染与业务逻辑隔离,一致的用户体验
•代码理解和调试——LSP和DAP两大协议
LSP协议:1、节制的设计 2、合理的抽象 3、周全的细节。
•集大成的 Remote Development
VSCRD:响应迅速、沿用本地设置、数据传输开销小、第三方插件可用、远程文件系统可用
vscode常用快捷键:
打开文件夹( Ctrl/⌘+O)和关闭文件夹工作区( Ctrl/⌘+K F)
新建文件:ctrl + N,保存文件:ctrl+s 关闭文件:ctrl+w
文件内搜索:ctrk+f
关闭所有文件: 关闭已保存的文件ctrl+k+u,ctrl+k+w
单行注释:CTRL+/ 多行注释:CTRL+shift+A
二、git
git 基本运行逻辑如下图所示
场景一:本地git的基本用法:
git init 初始化仓库
git status 展示仓库状态
git add [FILES] # 把文件添加到暂存区(Index)
git commit -m "wrote a commit log infro” # 把暂存区里的文件提交到仓库
git log 查看当前提交日志
git reset commit-id 回退
git reflog 查看head之后的提交记录
git reset
场景二:git远程的基本用法
git clone 从远程库中克隆到本地目录下
git fetch 抓取一个本地对象
git push 将本地文件更新到远程仓库
git merge 合并分支
git pull 推送到远程仓库
场景三:团队项目中的分叉合并
建议团队项目的每一个开发者都采用的工作流程大致如下:
1 克隆或同步最新的代码到本地存储库;
git clone https://DOMAIN_NAME/YOUR_NAME/REPO_NAME.git
git pull
2 为自己的工作创建一个分支,该分支应该只负责单一功能模块或代码模块的版本控制;
git checkout -b mybranch
git branch
3 在该分支上完成某单一功能模块或代码模块的开发工作;
git add FILES
git commit -m "commit log"
4 最后,将该分支合并到主分支。
git checkout master
git pull
git merge --no-ff mybranch
git push
默认的合并方式为"快进式合并"(fast-farward merge),会将分支里commit合并到主分支里,合并成一条时间线,与我们期望的呈现为一段独立的分支线段不符,因此合并时需要使用--no-ff参数关闭"快进式合并"(fast-farward merge)。
场景四git rebase
一般我们在软件开发的流程中,有一个朴素的版本管理哲学:开发者的提交要尽量干净、简单。开发者要把自己的代码修改按照功能拆分成一个个相对独立的提交,一个提交对应一个功能点,而且要在对应的 commit log message 里面描述清楚。因此在合并和 push 之前检查修改一下 commit 记录时常需要。
命令格式
git rebase -i [startpoint] [endpoint]
场景五:Fork + Pull request
1.先 fork(分叉) 别人的仓库,相当于拷贝一份;
2.做一些 bug fix或其他的代码贡献;
3.发起 Pullrequest 给原仓库;
- 原仓库的所有者review Pull request,如果没有问题的话,就会 merge Pull request 到原仓库中。
三、vim
vim不同模式
基本搜索替换:
word 向光标之下寻找一个名称为 word 的字符串。例如要在档案内搜寻 vbird 这个字符串,就输入 /vbird 即可!
n1,n2s/word1/word2/g n1 与 n2 为数字。在第 n1 与 n2 行之间寻找 word1 这个字符串,并将该字符串取代为 word2 !举例来说,在 100 到 200 行之间搜寻 vbird 并取代为 VBIRD 则:『:100,200s/vbird/VBIRD/g』。(
vim快速注释:
•批量注释:Ctrl + v 进入块选择模式,然后移动光标选中你要注释的行(VSCode可以鼠标选择代码块),再按大写的 I 进入行首插入模式输入注释符号如 // 或 #,输入完毕之后,按两下 ESC,Vim 会自动将你选中的所有行首都加上注释,保存退出完成注释。
•取消注释:Ctrl + v 进入块选择模式,选中你要删除的行首的注释符号,注意 // 要选中两个,选好之后按 d 即可删除注释,ESC 保存退出。
四、RegEx
为什么使用正则表达式
1测试字符串内的模式。例如,可以测试输入字符串,以查看字符串内是否出现电话号码模式或信用卡号码模式
2.替换文本。可以使用正则表达式来识别文档中的特定文本,完全删除该文本或者用其他文本替换它
通配符的基本用法:
“.”表示任意一个字符;
“?”表示前一个字符是否存在,也就是存在 0 次或 1 次;
“+”表示前一个字符出现一次或多次;
“*”表示前一个字符出现 0 次、1 次或多次。
常见的快捷方式
1.快捷方式\w 匹配字母数字[A-Za-z0-9_]。这个character sets 字符集匹配大小写字母加数字。注意,这个character sets 字符集还包括下划线字符“_”。
2.快捷方式\W 搜索\w 的相反方向。需要注意相反的模式使用大写字母。此快捷方式与[^A-Za-z0-9_]相同。
3.快捷方式\d 搜索数字字符集[0-9]。
4.快捷方式\D查找非数字字符,等于字符集[^0-9]。
括号的使用方法:
大括号表示限制字符的出现次数:
• • 要匹配字符串"aaah"中出现 3 到 5 次的 a,你的正则表达式将是a{3,5}h;
• • 仅匹配字符串"haaah"与至少出现 3 次的字母 a,正则表达式将是/ha{3,}h;
• • 为了仅匹配"hah"中出现 3 次的字母 a,你的正则表达式将是/ha{3}h。
中括号可以限制要查找的元素的范围:
例如,你要匹配"bag","big"和"bug",而不是"bog"。你可以创建正则表达式/b[aiu]g 来执行此操作。[aiu]是只匹配字符"a","i"或"u"的 character sets 字符集。
连字符“-”定义要匹配的字符范围:字符集[0-5]匹配 0 和 5 之间的所有数字,包括 0 和 5
字符“^”定义不想要匹配的字符,称为negated character sets 否定字符集,例如[^aeiou]排除元音的所有字符。
()可以使用捕获组
贪婪匹配、懒惰匹配
greedy 贪婪匹配找到符合正则表达式模式的字符串的最长可能部分,并将其作为匹配返回。相反还有 lazy 懒惰匹配,是找到符合正则表达式模式的字符串的最小可能部分。
例如 t[a-z]*i匹配titanic 懒惰匹配匹配ti,贪婪匹配匹配titanti
第二部分:代码中的软件工程
一、代码规范和代码风格
代码风格:规范整洁、逻辑清晰、优雅、
代码风格规范总结
缩进:4个空格,行宽:< 100个字符
代码行内要适当多留空格,如“=”、“+=” “>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等二元操作符的前后应当加空格。对于表达式比较长的for语句和if语句,为了紧凑起见可以适当地去掉一些空格,如for (i=0; i<10; i++)和if ((a<=b) && (c<=d));
在一个函数体内,逻揖上密切相关的语句之间不加空行,逻辑上不相关的代码块之间要适当留有空行以示区隔;
不要把多条语句和多个变量的定义放在同一行;
命名:
类型的成员变量通常用m_或者_来做前缀以示区别
一般变量名、对象名等使用LowerCamel风格,即第一个单词首字母小写,之后的单词都首字母大写,第一个单词一般都表示变量类型,比如int型变量iCounter;
类型、类、函数名等一般都用Pascal风格,即所有单词首字母大写
函数名一般使用动词或者动宾短语,如get/set,RenderPage
注释:
注释也要使用英文,不要使用中文或特殊字符,要保持源代码是ASCII字符格式文件
不要解释程序是如何工作的,要解释程序做什么,为什么这么做,以及特别需要注意的地方
每个源文件头部应该有版权、作者、版本、描述等相关信息
二、模块化软件设计
模块化的基本原理:是在软件系统设计时保持系统内各部分相对独立,以便每一个部分可以被独立地进行设计和开发。这个做法背后的基本原理是关注点的分离 (SoC, Separation of Concerns),
耦合度是指软件模块之间的依赖程度,一般可以分为紧密耦合(Tightly Coupled)、松散耦合(Loosely Coupled)和无耦合(Uncoupled)。
内聚度是指一个软件模块内部各种元素之间互相依赖的紧密程度。理想的内聚是功能内聚,也就是一个软件模块只做一件事,只完成一个主要功能点或者一个软件特性
三、可重用软件设计
3.1生产者重用与消费者重用
消费者重用是指软件开发者在项目中重用已有的一些软件模块代码,以加快项目工作进度。生产者重用即生产可以重用的软件模块。
-
软件开发者在重用已有的软件模块代码时一般会重点考虑如下四个关键因素:
•该软件模块是否能满足项目所要求的功能;
•采用该软件模块代码是否比从头构建一个需要更少的工作量,包括构建软件模块和集成软件模块等相关的工作;
•该软件模块是否有完善的文档说明;
•该软件模块是否有完整的测试及修订记录;
3.2接口的基本概念
接口就是互相联系的双方共同遵守的一种协议规范,在我们软件系统内部一般的接口方式是通过定义一组API函数来约定软件模块之间的沟通方式。
接口的五要素:
•接口的目的;
•接口使用前所需要满足的条件,一般称为前置条件或假定条件;
•使用接口的双方遵守的协议规范;
•接口使用之后的效果,一般称为后置条件;
接口所隐含的质量属性
一个接口案例:
tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable)
接口的目的由函数名定义:获取链表的头节点
接口必须满足的前置条件为链表不为空
接口需要遵守的协议由pLinkTable与tLinkTableNode的内部结构决定
接口的效果(后置条件)为取得了链表的头节点
接口的隐含质量为延时等
3.3微服务接口
微服务的概念:由一系列独立的微服务共同组成软件系统的一种架构模式
每个微服务单独部署,跑在自己的进程中,也就是说每个微服务可以有一个自己独立的运行环境和软件堆栈
每个微服务为独立的业务功能开发,一般每个微服务应分解到最小可变产品(MVP),达到功能内聚的理想状态。微服务一般通过RESTful API接口方式进行封装
系统中的各微服务是分布式管理的,各微服务之间非常强调隔离性,互相之间无耦合或者极为松散的耦合,系统通过前端应用或API网关来聚合各微服务完成整体系统的业务功能
产生原因:虚拟化与云计算技术
RESTful API:
•GET用来获取资源;
•POST用来新建资源(也可以用于更新资源);
•PUT用来更新资源;
•DELETE用来删除资源。
3.4 接口与耦合
接口与耦合度之间的关系:一般可以分为紧密耦合(Tightly Coupled)、松散耦合(Loosely Coupled)和无耦合(Uncoupled)。一般在软件设计中我们追求松散耦合。
耦合度依次递增可以分为无耦合、数据耦合、标记耦合、控制耦合、公共耦合和内容耦合。划分的标志与接口的定义方式:
•公共耦合
•当软件模块之间共享数据区或变量名的软件模块之间即是公共耦合,显然两个软件模块之间的接口定义不是通过显式的调用方式,而是隐式的共享了共享了数据区或变量名。
•数据耦合
•在软件模块之间仅通过显式的调用传递基本数据类型即为数据耦合。
•标记耦合
•在软件模块之间仅通过显式的调用传递复杂的数据结构(结构化数据)即为标记耦合,这时数据的结构成为调用双方软件模块隐含的规格约定,因此耦合度要比数据耦合高。但相比公共耦合没有经过显式的调用传递数据的方式耦合度要低。
3.5接口的定义方式
参数化上下文、移除前置条件、简化后置条件
案例:l例如对于以下代码
int a = 1;
int b = 2;
int c = 3;
int sum()
{
return a + b + c;
}
参数化上下文:避免对于上下文隐含的依赖条件:
int sum(int a, int b, int c);
移除前置条件:这里定义的接口仍然只能对三个数求和 因此:
int sum(int numbers[], int len);
,此时这个接口的后置条件也较为复杂,可能是只对numbers数组前len个数求和,所以后置条件不仅是返回值,还隐含了这个返回值是numbers数组前len个数的和。如果编程语言支持自动求数组长度:
int sum(int numbers[]);
3.6Menu案例中的回调函数
回调函数在数据结构层linklist.h中实现,其接口的定义与实现如以下代码所示:
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Condition(tLinkTableNode * pNode))
{
if(pLinkTable == NULL || Condition == NULL)
{
return NULL;
}
tLinkTableNode * pNode = pLinkTable->pHead;
while(pNode != pLinkTable->pTail)
{
if(Condition(pNode) == SUCCESS)
{
return pNode;
}
pNode = pNode->pNext;
}
return NULL;
}
在业务层menu.c当中与之匹配的函数设计如下
int SearchCondition(tLinkTableNode * pLinkTableNode)
{
tDataNode * pNode = (tDataNode *)pLinkTableNode;
if(strcmp(pNode->cmd, cmd) == 0)
{
return SUCCESS;
}
return FAILURE;
}
/* find a cmd in the linklist and return the datanode pointer */
tDataNode* FindCmd(tLinkTable * head, char * cmd)
{
return (tDataNode*)SearchLinkTableNode(head, SearchCondition);
}
//业务层中的一个使用案例
printf("Input a cmd number > ");
scanf("%s", cmd);
tDataNode *p = FindCmd(head, cmd);
在这种方式中,linklist.c中使用了一个回调函数Condition,这个函数位于Menu.c当中,使得我们的程序当中Menu.c中的cmd变量不必再流向linklist中的函数。linklist中的SearchLinkTableNode只需要得到业务层中的函数返回的结果即可。
这种设计的好处在于提高了数据结构的通用性,同时向上层隐藏了本层数据结构的实现细节,提高了程序的安全性。
四、可重入函数与线程安全
可重入(reentrant)函数可以由多于一个任务并发使用,而不必担心数据错误。
相反,不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关键部分禁用中断)。
可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入函数要么使用局部变量,要么在使用全局变量时保护自己的数据
int g = 0;
int function()
{
g++; /* switch to another thread */
printf("%d", g);
}
int function2(int a)
{
a++;
printf("%d", a);
}
线程安全:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全
可重入的函数不一定是线程安全的,可能是线程安全的也可能不是线程安全的;可重入的函数在多个线程中并发使用时是线程安全的,但不同的可重入函数(共享全局变量及静态变量)在多个线程中并发使用时会有线程安全问题;
第三部分:从需求分析到软件设计
一、需求与需求分析
需求:(用户的)期望的(软件的)行为的(软件开发者)表述
需求分析:需求分析就是需求分析师对用户期望的软件行为进行表述,并进一步用对象或实体的状态、属性和行为来定义需求
需求的类型:
1:功能需求:根据所需的活动描述所需的行为
2:质量需求/非功能需求 :描述软件必须具有的某些质量属性
3:设计约束:描述软件的设计决策,例如支持平台或接口组件
4:过程约束:描述可用与开发软件的技术或资源的限制
需求相关人员:客户(client)(支付软件开发成本)、客户(customer)(软件开发完成后再付费),软件用户、领域专家、律师或审计员、软件工程师
获取需求的方法:询问客户、查阅文档、观察现有系统、用户调查等等
- 高质量需求的特点:
- 需求可测试:对形容词与副词定量描述,用实体名称代替代词、确保名词已被清晰定义
- 解决冲突:将各类需求按重要性做分类处理
- 满足需求特点:正确性、可行性、完备性、一致性、无二义性等等
二、对需求进行分析与建模
需求分析主要包括原型化方法与建模的方法,原型化的方法有利于快速整理出用户接口,建模的方法可以可以快速给出有关事件发生顺序或活动同步约束的问题,建模的方法分为用例建模、业务领域建模与业务数据建模三种,下面将给出三种方式的详细说明:
2.1用例建模
用例的概念:
用例是一个业务过程,主要包含一下三个要素:
• A use case is initiated by (or begins with) an actor. 一个用例应该由业务领域内的某个参与者(Actor)所触发。
• A use case must accomplish a business task (for the actor).用例必须能为特定的参与者完成一个特定的业务任务。
•A use case must end with an actor. 一个用例必须终止于某个特定参与者,也就是特定参与者明确地或者隐含地得到了业务任务完成的结果。
用例的三个抽象层级:
抽象用例:一个动名词短语指定要做什么,就是一个抽象用例
高层用例:给用例划定了一个边界,说明清除用例什么时候开始、结束
拓展用例:一般使用一个两列的表格将参与者与待开发软件系统之间的每一个交互状态详细的列出
例如:打电话就是一个抽象用例,打电话”这一用例的开始状态就是用户拿起电话机听筒准备拨号,终止状态就是用户听到了接通电话的铃声反馈。这样就描述了一个高层用例。下图则描述了一个拓展用例:
用例建模的基本步骤
提取用例→找出用例边界→描述用例与参与者的上下文关系并画出用例图→列举拓展用例
用例图的画法
2.2 业务领域建模
业务领域建模的步骤:
搜集业务领域相关信息→找出业务领域相关概率→确定这些概念哪些属于类,哪些属于属性,以及相关关系→画出UML图
2.3 面向对象分析
对象与属性的区别在于对象在业务领域类一般是可以独立存在的
类和对象的基本画法
类与类之间的关系
继承关系:继承关系表达着两个概念之间具有概括化/具体化,例如小汽车和巴士是交通工具的继承关系
聚合关系:聚合关系表示一个对象是另一个对象的一部分的情况,例如汽车和汽车的引擎
关联关系:关联关系表示继承和聚合以外的一般关系,例如教师参与了退休计划
关联类及数据模型
通过引入一个关联类来实现定义关联关系相关的属
以下的对象图表示了学生Alex与Eric在ai与oose两门课程中取得的成绩情况:
使用一下的伪代码可以描述关联类的定义以及使用
class Student { ... }
class Course {...}
class Enroll {
private:
char grade;
Student* student;
Course* course;
public:
Enroll (Student* s, Course* c);
char getGrade();
void setGrade(char grade);
}
Enroll::Enroll(Student* s, Course* c) {
student=s; course=c;
三、从需求分析到软件设计
3.1敏捷统一过程
瀑布模型:将软件开发分为需求分析、设计、编码、测试、部署五个阶段
敏捷统一过程:核心在于用例驱动、以架构为中心(保持架构稳定)、迭代且增量三个原则
瀑布模型与敏捷统一过程的关系可以使用下图来描述:
敏捷统一过程可以分为计划阶段与增量阶段:
-
在增量阶段:主要分为一下5个阶段
• 用例建模(Use case modeling);
• 业务领域建模(Domain modeling);
• 对象交互建模(Object Interaction modeling);
• 形成设计类图(design class diagram);
• 软件的编码实现和软件应用部署;
3.2对象交互建模
基本步骤:
找出关键步骤进行剧情描述→转换为剧情描述表→转换为序列图→分析序列图、设计序列图
组织编排剧情的基本指导原则
KISS原则:保证剧情编排足够简洁,具体的细节留到编码阶段做处理
形成设计类图的基本步骤
类图是从领域模型与序列图中导出的,设计类图展示了软件中的类、类的属性、以及类与类之间的关系。可能会包含消息类、GUI类等等
设计流程
1从序列图中找到类→确定序列图的包含的方法及所属类→确定序列图中包含的属性及其所属类→确定类与类之间的关系
第四部分:软件科学基础概论
一、软件的基本构成要素
软件的基本构成要素:对象(Object),函数和变量/常量,指令与操作数
软件的基本结构
• 顺序结构:只要按照解决问题的顺序写出相应的语句就行,它的执行顺序是自上而下,依次执行。
• 分支结构:分支结构是在顺序结构的基础上,利用影响标志寄存器上标志位的指令和跳转指令组合起来借助于标志寄存器或特定寄存器暂存条件状态实现分支结构
• 循环结构:是指在程序中需要反复执行某个功能而设置的一种程序结构,可以看成是一个条件判断语句和一个向前无条件跳转语句的组合
• 函数调用框架:函数内部可以是顺序结构、分支结构和循环结构的组合。堆叠起来的堆栈数据用来记录函数调用框架结构的信息,是函数调用框架的灵魂
• 继承和对象组合:继承和对象组合都是以对象(类)作为软件基本元素构成的程序结构。对象是基于属性(变量或对象)和方法(函数)构建起来的更复杂的软件基本元素,显然它涵盖了前述包括函数调用框架在内所有程序结构,它是一个更高层、更复杂的抽象实体。基于对象(类)之间的继承关系所形成的两个对象各自独立、两个类紧密耦合的程序结构;对象组合则将一个对象作为另一个对象的属性,从而形成两个对象(类)之间有明确的依赖关系,下图可以清晰地对比继承和对象组合的特点。
软件中的一些特殊机制:
•回调函数:把函数的指针(地址)作为参数传递给另一个函数,当这个指针调用其所指向的函数时,就称这是回调函数:
•多态:简单的说,可以理解为允许将不同的子类类型的对象动态赋值给父类类型的变量,通过父类的变量调用方法在执行时实际执行的可能是不同的子类对象方法,因而表现出不同的执行效果
•闭包:该函数执行所需的上下文环境也作为返回的函数对象的一部分,这样该函数对象就是一个闭包。
•异步调用:
ES6原生提供了Promise对象。所谓Promise对象,就是代表了未来某个将要发生的事件,通常是一个异步操作。Promise对象提供了一整套完整的接口,使得可以更加容易地控制异步调用。
var promise = new Promise(function(resolve, reject) {
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
promise.then(function(value) { // resolve(value)
// success
}, function(value) { // reject(error)
// failure
});
•匿名函数:lamda函数是函数式编程中的高阶函数,在我们常见的命令式编程语言中常常以匿名函数的形式出现,比如无参数的代码块{ code },或者箭头函数
以下这段代码展示了一个由promise对象生成的定时器
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
timeout(100).then(() => {
console.log('done');
});
软件易变性:
•S-system: 良好定义的系统
•Matrix manipulation矩阵运算
•P-system: 对问题的近似解构成与的系统
•Chess program
•E-system:嵌入在真实世界中,随真实世界变动而变动的系统
•Software to predict how economy functions (but economy is notcompletely understood)
二、设计模式
2.1常用设计模式
单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,典型的应用如数据库实例。
建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。主要应用于复杂对象中的各部分的建造顺序相对固定或者创建复杂对象的算法独立于各组成部分。
原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例,原型模式的应用场景非常多,几乎所有通过复制的方式创建新实例的场景都有原型模式。
代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。代理模式是不要和陌生人说话原则的体现,典型的应用如外部接口本地化将外部的输入和输出封装成本地接口,有效降低模块与外部的耦合度。
适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作
装饰(Decorator)模式:在不改变现有对象结构的情况下,动态地给对象增加一些职责,即增加其额外的功能。
外观(Facade)模式:为复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。比如线程池、固定分配存储空间的消息队列等往往都是该模式的应用场景。
策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。策略模式是多态和对象组合的综合应用
命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
职责链(Chain of Responsibility)模式:为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。通过这种方式将多个请求处理者串联为一个链表,去除请求发送者与它们之间的耦合。
中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。在现实生活中,常常会出现好多对象之间存在复杂的交互关系,这种交互关系常常是“网状结构”,它要求每个对象都必须知道它需要交互的对象。如果把这种“网状结构”改为“星形结构”的话,将大大降低它们之间的“耦合性”,这时只要找一个“中介者”就可以了。在软件的开发过程中,这样的例子也很多,例如,在 MVC 框架中,控制器(C)就是模型(M)和视图(V)的中介者,采用“中介者模式”大大降低了对象之间的耦合性,提高系统的灵活性。
观察者(Observer)模式:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为,这样所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式。
2.2设计原则
开闭原则:软件应当对扩展开放,对修改关闭
Liskov替换原则:(已废弃)
依赖倒置原则:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象,要面向接口编程,不要面向实现编程。
单一职责原则:单一职责原则规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分
迪米特法则:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性(不要和陌生人说话)
合成复用原则:它要求在软件复用时,要尽量先使用组合或者聚合关系来实现,其次才考虑使用继承关系来实现。
三、常用软件设计
3.1MVC架构
-
Model-View-Controller(模型-视图-控制器)
Model(模型)代表一个存取数据的对象及其数据模型.
View(视图)代表模型包含的数据的表达方式,一般表达为可视化的界面接口。
Controller(控制器)作用于模型和视图上,控制数据流向模型对象,并在数据变化时更新视图。控制器可以使视图与模型分离开解耦合。
控制器负责负责业务逻辑处理,这一结构属于典型的中介者模式。
目的:一般应用在具有用户交互界面的软件当中,要拓展功能时,容易引起其他功能的错误。使用MVC模式分离了软件的视图与核心数据,便于对软件进行修改。
3.2 MVVM架构
MVVM即 Model-View-ViewModel,最早由微软提出来,借鉴了桌面应用程序的MVC模式的思想,是一种针对WPF、Silverlight、Windows Phone的设计模式,目前广泛应用于复杂的Javacript前端项目中。
在前端页面中,把Model用纯JavaScript对象表示,View负责显示,两者做到了最大限度的分离。把Model和View关联起来的就是ViewModel。ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model。
优点:低耦合,便于测试,可重用性。
3.3软件架构的描述方法
管道-过滤器:管道-过滤器风格的软件架构是面向数据流的软件体系结构,最典型的应用是编译系统。 一个普通的编译系统包括词法分析器、语法分析器、语义分析与中间代码生成器、目标代码生成器等一系列对源代码进行处理的过程。
客户-服务:客户-服务模式的架构风格是指客户代码通过请求和应答的方式访问或者调用服务代码。这里的请求和应答可以是函数调用和返回值,也可以是TCP Socket中的send和recv,还可以是HTTP协议中的GET请求和响应。
P2P(peer-to-peer)架构是客户-服务模式的一种特殊情形,P2P架构中每一个构件既是客户端又是服务端,即每一个构件都有一种接口,该接口不仅定义了构件提供的服务,同时也指定了向同类构件发送的服务请求。这样众多构件一起形成了一种对等的网络结构,如图:P2P网络结构示意图。
发布-订阅:发布者和订阅者。如果订阅者订阅了某一事件,则该事件一旦发生,发布者就会发布通知给该订阅者。观察者模式体现了发布-订阅架构的基本结构。
CRUD:是创建(Create)、 读取(Read)、更新(Update)和删除(Delete)四种数据库持久化信息的基本操作的助记符,表示对于存储的信息可以进行这四种持久化操作
3.4 软件架构的描述方法
•分解视图 Decomposition View:分解视图用软件模块勾划出系统结构,往往会通过不同抽象层级的软件模块形成层次化的结构。一个分解视图可能包含一下部分:一系统、包、类、组件、库
•依赖视图 Dependencies View:依赖视图展现了软件模块之间的依赖关系。比如一个软件模块A调用了另一个软件模块B,那么我们说软件模块A直接依赖软件模块B。如果一个软件模块依赖另一个软件模块产生的数据,那么这两个软件模块也具有一定的依赖关系。
•泛化视图 Generalization View:泛化视图展现了软件模块之间的一般化或具体化的关系,典型的例子就是面向对象分析和设计方法中类之间的继承关系
•执行视图 Execution View:执行视图展示了系统运行时的时序结构特点,比如流程图、时序图等。执行视图中的每一个执行实体,一般称为组件(Component),都是不同于其他组件的执行实体。如果有相同或相似的执行实体那么就把它们合并成一个。
•实现视图 Implementation View:实现视图是描述软件架构与源文件之间的映射关系。比如软件架构的静态结构以包图或设计类图的方式来描述,但是这些包和类都是在哪些目录的哪些源文件中具体实现的呢
•部署视图 Deployment View:部署视图是将执行实体和计算机资源建立映射关系。这里的执行实体的粒度要与所部署的计算机资源相匹配,比如以进程作为执行实体那么对应的计算机资源就是主机,这时应该描述进程对应主机所组成的网络拓扑结构,这样可以清晰地呈现进程间的网络通信和部署环境的网络结构特点
•工作任务分配视图 Work-assignment View:工作分配视图将系统分解成可独立完成的工作任务,以便分配给各项目团队和成员。工作分配视图有利于跟踪不同项目团队和成员的工作任务的进度,也有利于在个项目团队和成员之间合理地分配和调整项目资源,甚至在项目计划阶段工作分配视图对于进度规划、项目评估和经费预算都能起到有益的作用
3.5高质量软件
软件质量的含义:
•生产商:产品符合标准规范
•消费者:产品适于使用且带来益处
产品视角下的软件质量
用户看到的产品质量和开发者看到的产品质量是不同的,我们将用户看到的产品质量称为外部质量,比如正确的功能、发生故障的数量等;开发者看到的产品质量称为内部质量。比如代码缺陷等。
几种重要的软件质量属性:
• 易于修改维护(Modifiability)
• 良好的性能表现(Performance)
• 安全性(Security)
• 可靠性(Reliability)
• 健壮性(Robustness)
• 易用性(Usability)
• 商业目标(Business goals)
第五部分:软件危机与软件过程
一、软件危机
软件危机的背景:1960年代中期,大容量、高速度计算机的出现,使计算机的应用范围迅速扩大,因此软件开发需求和软件的规模都急剧增长。高级语言开始出现、操作系统迅猛发展、大量数据处理导致数据库管理系统的诞生等,导致软件开发的基础环境发生了重大变化,从与计算机硬件基础环境直接打交道,变为在一个更高抽象层级之上编写软件。
没有银弹的含义:在10年内无法找到解决软件危机的杀手锏(银弹)
二、软件过程模型
软件的生命周期概述:
我们将软件的生命周期划分为:分析、设计、实现、交付和维护这么五个阶段
软件的故障率曲线:
软件过程描述分为描述性的和说明性的两种,
描述性的为:描述性的过程试图客观陈述在软件开发过程中实际发生什么
说明性的为:主观陈述在软件开发过程中应该会发生什么。显然说明性的过程是抽象的过程模型
瀑布模型
是第一个软件过程开发模型,对于能够完全透彻理解的需求且几乎不会发生需求变更的项目瀑布模型是适用的。也常被用于解释项目进展和开发的阶段
原型化的瀑布性:在瀑布模型的基础上增加一个原型化(prototyping)阶段,可以有效将风险前移,改善整个项目的技术和管理上的可控性。
V模型
V模型也是在瀑布模型基础上发展出来的,单元测试,集成测试与交付测试可以被看作是在不同层面验证设计,V模型将软件的前后两端连接在一起,提高了软件开发过程的内聚度
生死相依原则:在思考一项工作开始时,思考一项工作如何结束
分阶段的增量和迭代开发过程
这种开发模式可以是用户在软件开发完全完成之前就能使用到软件的部分功能
螺旋模型
-
螺旋模型将每一次迭代过程分为四个主要阶段:
• • Plan
• • Determine goals, alternatives and constraints
• • Evaluate alternatives and risks
• • Develop and test
三、个人软件过程(PSP)与团队软件过程(TSP)、
个体软件过程:
团队的概念:团队有一致的团队目标,要一起完成这个目标。团队成员有各自的分工
团队组织与开发项目特点的关系:
团队的基本要素:团队规模,团队凝聚性,团队
四、CMM/CMMI
CMM:一个评估软件过程成熟度的框架
- CMM五级的分别的定义如下所示:
- 初始级,在初始级水平上,软件组织对项目的目标与要做的努力很清晰,项目的目标可以实现
- 管理级,另外,软件组织在项目实施上能够遵守既定的计划与流程,有资源准备,权责到人,对项目相关的实施人员进行了相应的培训,对整个流程进行监测与控制,并联合上级单位对项目与流程进行审查。
- 已定义级:另外,软件组织能够根据自身的特殊情况及自己的标准流程,将这套管理体系与流程予以制度化
- 量化管理级:另外,软件组织的项目管理实现了数字化。通过数字化技术来实现流程的稳定性,实现管理的精度,降低项目实施在质量上的波动
- 持续优化级:,软件组织能够充分利用信息资料,对软件组织在项目实施的过程中可能出现的问题予以预防。能够主动地改善流程,运用新技术,实现流程的优化。
五、敏捷统一方法
敏捷统一方法产生的背景:到2000年之后的互联网时代,很多软件都是通过网络服务器端实现的,有各种方便的推送渠道快速直达客户端。互联网使得知识的获取变得更加容易,很多软件可以由一个小团队来实现。
-
敏捷宣言:
• • 个体和互动 高于 流程和工具
• • 工作的软件 高于 详尽的文档
• • 客户合作 高于 合同谈判
• • 响应变化 高于 遵循计划
六、DevOps
含义:DevOps(Development和Operations的组合)是一组过程、方法与系统的统称,用于促进软件开发、技术运营和质量保障(QA)部门之间的沟通、协作与整合