0%

简介

我将会在这个博客写一些技术文章,包括算法、后端、计算机网络等等。此外,可能还会写一些其他的,比如体育、电影等等。欢迎大家访问哈哈哈哈

第一篇博客,上我最喜欢的一首词吧

沁园春-长沙

独立寒秋,湘江北去,橘子洲头。
看万山红遍,层林尽染;漫江碧透,百舸争流。
鹰击长空,鱼翔浅底,万类霜天竞自由。
怅寥廓,问苍茫大地,谁主沉浮?
携来百侣曾游。忆往昔峥嵘岁月稠。
恰同学少年,风华正茂;书生意气,挥斥方遒。
指点江山,激扬文字,粪土当年万户侯。
曾记否,到中流击水,浪遏飞舟?

美团

主要问项目,算法题数组求和

快手

主要问项目,算法题大数相加(String)

OPPO

主要问项目,涉及SpringCloud服务治理,分布式相关

联通智网

计网,简单八股,项目简要介绍

BOSS直聘

八股,算法题手写单例模式,反馈沟通不积极被挂

新凯来,DigiFinex,法大大

小公司,面试都偏八股,基础知识较多

同花顺

八股,线程顺序执行。项目,算法题讨论,智力题

TpLink

问题都比较简单,时间也短

TCL

问题基本都是八股,但深度足够

2022 4 15

大概是要去Tp了,大厂有缘再战!!!

考试要求

要求1:基本概念
要求2:数据集划分
要求3:性能度量
要求4:可以描述各个任务之间的关系

线性回归:

要求1:可以按照自己的理解简述线性回归问题。
要求2:可以对简单数据进行计算(PPT中例题)。
要求3: 可以编程实现线性回归算法。

逻辑回归:
要求1:可以按照自己的理解简述逻辑回归问题以及与线性回归问题的区别与联系。
要求2:掌握梯度下降法、牛顿法的基本原理和迭代公式。
要求3:可以编程实现逻辑回归算法。

决策树:

要求1:可以按照自己的理解简述决策树算法。
要求2:可以利用ID3,C4.5 和 CART算法对数据进行分类。
要求3:可以对生成的决策树进行剪枝处理。

感知机:
要求1:可以按照自己的理解简述感知机模型。
要求2:可以利用感知机解决逻辑分类问题(PPT例子)。

神经网络:

要求1:可以按照自己的理解简述神经网络模型,以及与感知机的关系。
要求2:掌握BP算法的基本原理和迭代公式。

支持向量机:

要求1:可以按照自己的理解简述支持向量机模型,以及与其他分类算法的区别。
要求2:掌握使用拉格朗日乘子法对约束优化问题进行求解,并理解使用拉格朗日乘子法求解SVM问题的原因。
要求3:可以按照自己的理解简述软间隔支持向量机,并分析其与常规支持向量机的关系与区别。
要求4: 了解SMO算法。

主成分分析:

要求1:可以按照自己的理解简述主成分分析算法。
要求2:可以简述PCA算法的流程。
要求3:核化PCA与PCA的相同与不同。

线性判别分析:

要求1:可以按照自己的理解简述线性判别分析算法,并分析其与PCA之间的联系与区别。
要求2:可以简述LDA算法的流程。

K-均值聚类:

要求1:可以按照自己的理解简述K-means算法。
要求2:可以简述Lloyd算法的流程。

2021 06 27 23 08 14

线性回归

泛化(generalization):在训练集上训练好的模型在未见样本上的效果

NFL定理(No Free Lunch):一个算法 A 若在某些问题上比另一个算法 B 好,必存在另一些问题 B 比 A 好。

过拟合是指模型在训练集上表现很好,到了验证和测试阶段就大不如意了,即模型的泛化能力很差。

错误率:有多少比例的西瓜被判断错误;

查准率(precision):算法挑出来的西瓜中有多少比例是好西瓜;

查全率(recall):所有的好西瓜中有多少比例被算法跳了出来。

继续按照上述前提,对于二分类问题,我们根据真实类别与算法预测类别会有下面四个名词:

在写下面四个名词前,需要给一些关于T(true)、F(false)、P(positive)、N(negative)的解释:P表示算法预测这个样本为1(好西瓜)、N表示算法预测这个样本为0(坏西瓜);T表示算法预测的和真实情况一样,即算法预测正确,F表示算法预测的和真实情况不一样,即算法预测不对。

TP:正确地标记为正,即算法预测它为好西瓜,这个西瓜真实情况也是好西瓜(双重肯定是肯定);
FP:错误地标记为正,即算法预测它是好西瓜,但这个西瓜真实情况是坏西瓜;
FN:错误地标记为负,即算法预测为坏西瓜,(F算法预测的不对)但这个西瓜真实情况是好西瓜(双重否定也是肯定);
TN:正确地标记为负,即算法标记为坏西瓜,(T算法预测的正确)这个西瓜真实情况是坏西瓜。

所以有:

20180716153628519

查准率和查全率是一对矛盾的指标,一般说,当查准率高的时候,查全率一般很低;查全率高时,查准率一般很低。比如:若我们希望选出的西瓜中好瓜尽可能多,即查准率高,则只挑选最优把握的西瓜,算法挑选出来的西瓜(TP+FP)会减少,相对挑选出的西瓜确实是好瓜(TP)也相应减少,但是分母(TP+FP)减少的更快,所以查准率变大;在查全率公式中,分母(所有好瓜的总数)是不会变的,分子(TP)在减小,所以查全率变小。
在实际的模型评估中,单用Precision或者Recall来评价模型是不完整的,评价模型时必须用Precision/Recall两个值。这里介绍三种使用方法:平衡点(Break-Even Point,BEP)、F1度量、F1度量的一般化形式。

2021 06 27 13 30 08

线性回归,公式1

2021 06 26 22 48 08

线性回归算法学习了一条直线,或者说两个参数(Parameters)
可以利用线性回归算法对未知数据进行预(Prediction)
线性回归的效果主要取决于数据本身的分布情况(Distribution)

公式2

2021 06 27 10 41 11

2021 06 27 10 41 31

2021 06 27 10 53 59

2021 06 27 13 01 35

逻辑回归

线性回归解决的是回归问题,逻辑回归相当于是线性回归的基础上,来解决分类问题。

将线性回归变成一个0~1输出的分类问题

区别:

1.线性回归用来预测连续的变量(房价预测),逻辑回归用来预测离散的变量(分类,癌症预测)
2. 线性回归是拟合函数,逻辑回归是预测函数
3. 线性回归的参数计算方法是最小二乘法,逻辑回归的参数计算方法是似然估计的方法

2021 06 27 13 15 50

2021 06 27 13 06 14

决策树

2021 06 27 13 52 04

2021 06 27 13 53 05

2021 06 27 13 54 56

2021 06 27 14 31 23

2021 06 27 14 00 28

2021 06 27 14 01 32

2021 06 27 14 02 28

2021 06 27 14 28 28

2021 06 27 14 38 07

2021 06 27 23 25 15

感知机

感知机由两层神经元组成。输入层接受外界输入信号后传递给输出层,输出层是MP神经元。感知机能容易的实现与或非运算。

需要注意的是,感知机无法处理非线性可分问题

2021 06 27 16 16 39

感知机的目标是使误分类点的个数为0,所以可以采用函数间隔,简化学习过程

两层感知机解决异或问题

2021 06 27 16 26 36

神经网络

神经网络(Neural Network):
神经网络是由具有适应性的简单单元组成的广泛并行互连的网络,它的组织能够模拟生物神经系统对真实世界物体所做的交互反应。

2021 06 27 16 28 20

2021 06 27 16 36 13

2021 06 27 16 37 49

2021 06 27 16 38 43

支持向量机

v2 e833772fe2044ad9c353fb0173bd0b79 1440w

线性可分

首先我们先来了解下什么是线性可分。

v2 a75409cca671ad0819cd28ff9f40a01b 720w

在二维空间上,两类点被一条直线完全分开叫做线性可分。

严格的数学定义是:

[公式][公式] 是 n 维欧氏空间中的两个点集。如果存在 n 维向量 w 和实数 b,使得所有属于 [公式] 的点 [公式] 都有 [公式] ,而对于所有属于 [公式] 的点 [公式] 则有 [公式] ,则我们称 [公式][公式] 线性可分。

最大间隔超平面

从二维扩展到多维空间中时,将 [公式][公式] 完全正确地划分开的 [公式] 就成了一个超平面。

为了使这个超平面更具鲁棒性,我们会去找最佳超平面,以最大间隔把两类样本分开的超平面,也称之为最大间隔超平面。

  • 两类样本分别分割在该超平面的两侧;
  • 两侧距离超平面最近的样本点到超平面的距离被最大化了。

支持向量

v2 0f1ccaf844905148b7e75cab0d0ee2e3 720w

样本中距离超平面最近的一些点,这些点叫做支持向量

SVM 最优化问题

2021 06 27 19 53 55

2021 06 27 19 54 56

2021 06 27 19 55 19

2021 06 27 19 55 52

2021 06 27 20 00 24

2021 06 27 19 56 47

2021 06 27 20 03 45

逻辑回归算法是基于全部样本的二分类器:考虑全部样本的平均似然性。
支持向量机算法是基于部分样本的二分类器:考虑部分靠近边界的支持向量

拉格朗日乘子法

2021 06 27 20 08 02

对偶问题

推导过程省略….

2021 06 27 20 24 27

软间隔SVM

2021 06 27 20 28 59

主成分分析

(Principal Component Analysis, PCA)

PCA是一个无监督降维方法(Unsupervised DR), 并 没 有 运 用 任 何 有 监 督 信 息 ( Supervised Information),这也是PCA算法一个弊端

2021 06 27 20 52 44

2021 06 27 20 56 20

2021 06 27 21 03 58

2021 06 27 21 05 11

线性判别分析

LDA的基本思想给定训练样本,设法将样本投影到一条直线上,使得同类样例的投影点尽可能的接近,异类样例的投影点尽可能的远。

2021 06 27 21 15 10

2021 06 27 21 17 58

2021 06 27 21 18 49

kernal pca:一种用核映射的方式将特征从低维映射到高维;可以类比支持向量机;然后再选择主成分;是一种非线性的

传统的pca或者svd是线性的处理方式

K-均值聚类

2021 06 27 21 23 40

2021 06 27 21 25 07

聚类的“好坏”不存在绝对标准

2021 06 27 21 33 39

2021 06 27 21 29 55

2021 06 27 21 30 26

2021 06 27 21 30 48

2021 06 27 21 31 25

2021 06 27 21 31 51

一:
数字图像处理的应用

图像变换、图像增强、图像复原、图像压缩编码、图像分割

报纸业,航天技术(月球照片处理),遥感卫星,医学,气象预报,空间探索,军事领域,通信,

二:

没有色彩的光称为消色光,指的是白色,黑色和各种深浅程度不同的灰色。消色光的属性只有亮度或强度,通常用灰度级描述这种光的强度

图像的采样和量化:光信号->电信号->数字化

像素的邻域:4邻域,对角邻域,8邻域

连接:空间上临接且像素灰度值相似

屏幕截图

2021 06 14 12 00 51

三:
亮度等级范围

点运算:灰度变换增强(线性变换,非线性变换),直方图增强

非线性变换:对数变换(加亮,减暗),指数变换

四:

线性系统

离散傅里叶变换

自适应中值滤波:常规的中值滤波器的窗口尺寸是固定大小不变的,就不能同时兼顾去噪和保护图像的细节。这时就要寻求一种改变,根据预先设定好的条件,在滤波的过程中,动态的改变滤波器的窗口尺寸大小,这就是自适应中值滤波器 Adaptive Median Filter。在滤波的过程中,自适应中值滤波器会根据预先设定好的条件,改变滤波窗口的尺寸大小,同时还会根据一定的条件判断当前像素是不是噪声,如果是则用邻域中值替换掉当前像素;不是,则不作改变。

(a)通常,如果将低阶比特面设为零值,对一幅图像的直方 图有何影响?

答:如果将低阶比特面设为零值,该图像会丢失细节。即不 同灰度值的像素个数将减少,这会导致直方图的成分数减 少。由于像素个数不会改变,这将在总体上导致直方图峰值 高度上升。通常,较低的灰度值变化将减少对比度。

(b)如果将高阶比特面设为零值,对直方图有何影响?

答:如果将高阶比特面设为零值,该图像会丢失轮廓,即丢失视觉上的很多数据。最明显的影响是使图像非常模糊,根据灰度变换函数,将 0~127 之间的所有灰度映射为0,下降 的最高位将限制到127 位图像中最亮的水平。由于像素数将保持不变,一些直方图峰值的高度会增加。

(c)假定对一幅数字图像进行直方图均衡化处理,试说明:第二次均衡化处理的结果与第一次均衡化处理的结果相同

先直方图均衡了,就相当于所有的灰度级上的像素点数目相同,无论你再进行多少次直方图均衡,他始终都不再变化了,因为第一次就已经均衡了.

(d) 在给定的应用中,一个均值掩模被用于输入图像以减少噪声,然后再用一个拉普拉斯掩模来增强图像中的小细节,如果将这两个步骤交换一下,结果是否会相同?

均值掩模被用于输入图像以减少噪声,拉普拉斯掩模增强图像中的小细节。其中拉普拉斯掩模属于锐化处理,目的是突出图像中的细节或者增强被模糊了的细节。这种模糊不是由于错误操作,就是特殊图像获取方法的固有影响。将这两步骤交换,结果还是一样的。

(e)将高频加强和直方图均衡相结合是得到边缘锐化和对比度增强的有效方法。上述两个操作的先后顺序对结果有影响吗?为什么?

答:有影响,应先进行高频加强,再进行直方图均衡化。

高频加强是针对通过高通滤波后的图像整体偏暗,因此通过提高平均灰度的亮度,使图像的视觉鉴别能力提高。再通过直方图均衡化将图像的窄带动态范围变为宽带动态范围,从而达到提高对比度的效果。若先进行直方图均衡化,再进行高频加强,对于图像亮度呈现较强的两极现象时,例如多数像素主要分布在极暗区域,而少数像素存在于极亮区域时,先直方图均衡化会导致图像被漂白,再进行高频加强,获得的图像边缘不突出,图像的对比度较差。

图像增强

图像增强的目的

​ 改善图像的视觉效果,或者使图像更适合于人或机器进行分析处理

​ 通过图像增强,可以减少图像中的噪声,提高目标与背景的对比度

​ 强调或抑制图像中的某些细节

图像增强方法的分类

处理的作用域:空间域方法、频率域方法
空间域方法:在图像二维平面上,直接对像素值进行处理
频率域方法:对图像作Fourier变换,在变换域处理,再作逆变换得到增强图像

其他方法
• 小波变换
• Retinex

空间域方法:基于灰度变换的图像增强

灰度变换:将一个灰度区间映射到另一个灰度区间的变换称为灰度变换

2021 06 22 16 35 18

灰度变换可使图像动态范围加大,图像对比度扩展
只改变像素灰度值,不改变像素位置

空间域增强:非线性变换

幂次变换

2021 06 22 16 40 05

空间域增强:空间滤波增强

空间域滤波增强采用模板处理方法对图像进行滤波,去除图像噪声或增强图像的细节

空间域平滑滤波

高斯噪声去噪

2021 06 22 16 42 35

分析:任何一幅原始图像,在其获取和传输等过程中,会受到各种噪声的干扰,使图像模糊,对图像分析不利。
为了抑制噪声改善图像质量所进行的处理称图像平滑或去噪。

方法分类:

(1)局部平滑法

2021 06 22 17 03 30

2021 06 22 17 04 32

(2)超限像素平滑法

2021 06 22 17 05 45

(3)空间低通滤波法

2021 06 22 17 06 48

空间域锐化滤波

增强边缘和轮廓示例

2021 06 22 16 44 56

梯度锐化法
拉普拉斯算子
低频分量消减法

频率域增强:

平滑滤波:

2021 06 22 17 12 21

理想低通滤波器
巴特沃斯低通滤波器
高斯低通滤波器

频率域平滑-高斯低通滤波平滑

2021 06 22 16 48 04

2021 06 22 19 25 13

2021 06 22 19 25 33

锐化滤波:

2021 06 22 19 31 12

2021 06 22 19 31 48

2021 06 22 19 32 11

2021 06 22 19 32 30

2021 06 22 19 32 43

基于直方图处理的图像增强

灰度图像的直方图

灰度级直方图是图像的一种统计表达,它反映了该图中不同灰度级出现的统计概率

2021 06 22 16 52 55

2021 06 22 16 54 09

直方图均衡化
基本思想

把原始图像的直方图变换为均匀分布的形式,从而增加图像灰度的动态范围,达到增强图像对比度的效果。

经过均衡化处理的图像,其灰度级出现的概率相同,此时图像的熵最大,图像所包含的信息量最大。

方法推导
方法特点

2021 06 22 16 59 28

图像复原

图像复原及退化模型

2021 06 23 19 21 23

2021 06 23 19 22 00

2021 06 23 19 22 27

2021 06 23 19 22 59

噪声模型

2021 06 23 19 26 44

2021 06 23 19 27 02

2021 06 23 19 27 26

2021 06 23 19 27 59

空间域滤波复原

2021 06 23 19 48 56

2021 06 23 19 49 55

2021 06 23 19 50 11

2021 06 23 19 50 31

2021 06 23 19 51 24

2021 06 23 19 51 39

2021 06 23 19 51 56

2021 06 23 19 52 32

2021 06 23 20 13 39

2021 06 23 19 52 53

频率域滤波复原

2021 06 23 20 18 07

2021 06 23 20 18 21

2021 06 23 20 18 43

2021 06 23 20 19 08

2021 06 23 20 19 21

2021 06 23 20 19 34

估计退化函数

观察估计法、试验估计法、模型估计法。

逆滤波和维纳滤波

图像压缩

数字图像的压缩是指在不同用途的图像质量要求下,用最少的比特数表示一幅图像的技术。
数字图像的压缩是实现图像存储和传输的基础。
数字图像压缩目的:
节省图像存储容量;减少传输信道容量;缩短图像加工处理时间。

图像数据存在的冗余可分为三类:
① 编码冗余(coding redundancy);
② 像素间的冗余(interpixel redundancy);
③ 心里视觉冗余(psychovisual redundancy)。

chapter 2

1. 描述三个有特殊需求的用户群体。 对于这些人群中的每一个,提出三种可以改进当前界面以更好地为他们服务的方法

1)残疾用户

对于盲人或者眼睛有疾病导致视觉不好的人,可以使用文本到语音的转换,文本到语音的转换能够帮助盲人用户接受电子邮件或阅读文本文件,语音的设备更能允许某些用户界面进行语音控制的操作。这项技术同样可以用于虽然视力完好但是特殊情况下的人,例如开车、工作等。

对于听力障碍的用户,往往可以经过简单改动的计算机,实现声音到可视信号的转变。

对于身体障碍用户,需要大量特殊输入设备,这要取决于具体的障碍,语音技术和人眼跟踪设备、头戴式光电鼠标都可以被用来满足残疾用户的需要。世界上有名的例子就是伟大天文学家霍金和他的个人电脑。

2)老年用户

通过用户提供对字体大小、显示对比度和音量大小的控制、更清晰的导航路径、一致的布局和更简单的命令语言来设计界面,从而为老年人改进对界面内容的访问。还有诸如手眼协调、增强灵敏性和改进反应时间等作法,都能改善老年人的使用体验。另外,计算机游戏对老年人来说也是一个有吸引的东西,这可以让一些惧怕使用电脑的老年人获得体验机会而被吸引。老年人使用鼠标往往很迟钝,我们可以转换成高精度的触摸屏。

3)儿童

儿童的用户界面,强调娱乐性和教育性。儿童的用户界面往往与父母息息相关。首先要注意到界面的教育性,要为儿童的教育着想,可以附带丰富的知识内容、家长指南资料和教师培训资料等,尤其在艺术、音乐、写作和数学方面的教学资料。另外,设计需要考虑到儿童的局限性,要简单,易懂,避免复杂的序列。特别需要注意的是,在教育和父母对孩子的关注来说,避免对暴力、种族主义、色情资料还有儿童隐私陌生人威胁等问题的信息访问,这都是非常重要的

2 一些怀疑论者认为适应多样性需要简化或最小公分母的策略。 然而,其他人声称,根据他们的经验,重新思考界面设计以适应这些多样性情况将为所有用户带来更好的产品。 举一个产品的例子,它既满足了特定人群的特定需求,又为所有用户提供了更好的体验。

一个答案可能是作者在不同种族社区看到的自动取款机,允许用户在继续与机器交互之前选择语言

3 假设您需要为美国和中国的用户设计一个系统。 列出您应该注意的文化差异列表,以便做出成功的设计。

1)中国的用户来自沉思的传统文化,,他们更喜欢稳定显示的界面,而美国的用户都是行动导向的基于新奇文化的用户,他们更偏爱生动的屏幕和多次点击。

2)在高校主页来反应二者的文化差异,中国主要喜欢在主页上强调他们深刻印

象的建筑物和有名的师资,美国的高校主页则更多表现那种学生团队精神和活跃

的社交生活。

3)文本作为世界用户界面的一个关键因素,必须重视其中涉及到的文化差异,

这些差异因素主要表现在:

字符、数字、特殊符号和区分符

阅读方向(从左到右与从右到左、垂直阅览等)

日期和时间格式

货币格式

质量度量单位

电话号码和地址的格式

名字和头衔

社交保险、国家身份证、护照号码

大写和标点

排序

图标按钮颜色

多元化、语法和拼写

成规、政策、音调、礼节和隐喻

4)避免一些容易引起两国误解的元素或者民族政治冲突的敏感内容。

总结:以上是在给中美两国设计用户交互界面系统时候需要注意的一些问题,有时候要折中考虑,有时候又要兼并考虑,总而言之,这样的系统要求做到“人人均可获取、价格可以承受、适应本地语言和文化需求,并且支持可持续发展”。

4 建议三个可用性度量,可以直接用于产生一个系统的实际评估。在使用这些措施时,请牢记效率和满意度的目标

学习时间,人为失误率,任务完成速度,主观满意度,销量,长期的人力留存率。

5 简要说明界面设计的八项黄金法则。 举例说明您在违反这些规则的设备、计算机界面或网站上看到的示例。

八大黄金准则:

1.争取保持一致性;

2.满足普通可用性的需要;

3.提供信息反馈;

4.设计对话框以产生结束信息;

5.预防错误;

6.允许动作回退;

7.支持内部控制点;

8.减轻短期记忆负担;

对于这八大黄金准则,有一些反例子:

1)重庆大学新教务网,在一致性方面做得不好,其主页和登录后界面结构反差

很大,在选课期间第二条准则也不是很让人如愿,不过对于基本满足了其余的准则;

2)一些安卓手机游戏经过收费破解、汉化或者附加广告之后,往往容易异常退

出,退出的时候没有任何提示,这违反了上诉的 3 4 5点;

3)photoshop在热键设计的时候没有特别仔细考虑到用户的使用习惯,平时用

户习惯用的撤销热键ctrl+z并不是photoshop的撤销功能热键,而photoshop的撤销热键shift+ctrl+z却常常被作为QQ等其余软件的其余功能热键而产生冲突,

这样为了使用习惯有的人往往会耽误时间去寻找修改热键的教程,这样以来photoshop就违反了以上的 6 7点;

6 说出一个你经常使用的容易产生错误的软件。解释你可以改进界面以更好地防止错误的方法

不用说名字了,但是一些桌面操作系统供应商在没有完全测试他们的更改的情况下就发布了他们的产品更新,有时会比一开始不做更改更困难。我的建议是,更多地使用beta测试小组,这些人致力于在变更发布到大众面前之前及时、准确地做出反馈。

ubuntu存在更新内核后一些环境(比如python)失效,需要重新配置

chapter 3

设计概念模型:
界面隐喻、界面类型、所支持的活动、功能、功能之间的关系;
头脑风暴,情绪板,场景scenario,故事板,草图,etc., prototype)

chapter 4

1.描述至少三种不同类型的专家评审方法。

启发式评估
指导方针评审
一致性检验
认知走查
人类思维的隐喻
正规的可用性检查

2 一个反对当前接口设计的论点,流行的文字处理软件是所有功能菜单项一起出现,导致界面过于复杂。这种复杂性导致新手用户感到困惑和沮丧。另一种设计是提供不同层次的功能复杂性,用户可以选择适合自己的层次,然后随着对工具的熟悉而提升到更高的层次,从而感到更舒适 更有效地学习。你被要求进行可用性测试来比较这两个设计。
-在这种情况下应该使用哪种可用性测试?解释为什么。

回顾本章中讨论的所有可用性测试方法,并讨论它们在这里适用或不适用的原因。
可用性测试的范围包括:
a)纸质模型和原型设计
b)折扣可用性测试
c)竞争可用性测试
d)普遍可用性测试
e)现场测试和便携式实验室
f)远程可用性测试
g) Can-you-break-this测试
例如,一个纸质模型可能不能工作,因为大多数人已经熟悉一两个字处理应用程序。另一方面,竞争性可用性测试可能适合这种情况。

chapter 5

1 描述直接操作的三个原则

1)用有意义的视觉隐喻连续表示感兴趣的对象和动作。

2)用物理动作或按压有标签的按钮来取代复杂的语法。

3)使用快速的、增量的可逆动作,这些动作对感兴趣对象的影响是立刻可见的。

2 给出直接操作命令行接口的四个好处。还列出了直接操作的四个问题。

好处:
d)控制/显示兼容性
e)更少的语法减少了错误率
f)错误更容易预防
g)更快的学习和更高的记忆力
h)鼓励探索

问题:
a)可能增加系统资源
b)有些动作可能很繁琐
c)宏观技术往往薄弱
d)历史和其他追踪可能比较困难
e)视障用户可能会有更多困难

3 分析电子游戏的成功可以为界面设计师提供启发。解释我们可以从电子游戏界面设计中学到什么经验教训,但也要提供一些无效的例子

物理动作——如按键、操纵杆运动或旋钮旋转——在屏幕上产生快速反应。不需要记住语法,因此不存在语法错误消息。通常情况下,错误消息很少出现,因为操作的结果非常明显,并且可以很容易地逆转:如果用户将飞船向左移动得太远,他们只会使用自然的反向操作,即回到右边。这些已被证明能提高用户满意度的原则,可以应用于办公室自动化、个人计算或其他交互式环境。大多数游戏都会持续呈现数值分数,以便用户能够衡量自己的进程并与他人进行竞争。许多教育游戏有效地使用了直接操作。游戏玩家与系统或其他玩家进行竞争,而应用系统用户更喜欢强烈的内部控制,这让他们有一种掌控感。同样地,游戏玩家寻求娱乐并专注于挑战,而应用用户则专注于他们的任务,并可能讨厌太多好玩的干扰。大多数游戏中的随机事件都是为了挑战用户;然而,在非游戏设计中,可预测的系统行为是首选。

4 一家航空公司正在设计一个新的网上订票系统。他们希望添加一些直接操作特性。例如,他们希望客户单击地图以指定出发城市和目的地,并单击日历以指示他们的时间表。从你的角度,列出新想法与他们的旧系统相比的四个好处和四个问题,旧系统要求客户通过打字来完成工作。

好处:
a)学习时间可能会减少,但我们需要可用性研究来验证,特别是对于新手用户。
b)直观的界面(对于那些知道自己地理位置的人来说,即纽约在美国东北部——个人需要可视化纽约与波士顿(北面)或华盛顿特区在南面的位置)。
c)鼓励探索(度假航空旅行者的冒险意识)
d)语法错误更少
问题:
a)数据输入错误,例如出发或目的地指向错误的城市
b)错误的快速逆转,如纠正上述错误的城市选择
c)地图的大小将需要缩放到较小的机场位置,并可能需要一段时间的直接操作导航
d)视力受损的人很难看到

5 在紧凑性、性能速度和可学习性方面,比较命令语言界面和直接操作界面。

简洁 典型的文本对象比视觉对象占用更少的屏幕空间

性能速度任务的复杂性,重复性通常来说简单的任务在直接操作中运行的更快,而复杂的任务在命令行里运行的更快。重复的任务在命令行里更加简单。

用户的专业知识

一般新手用户在直接操作下执行任务比较快。但当一个用户得到更练习之后这种状况对于一些特定任务下就会发生改变。

错误频率

直接操作接口通常可以消除用户的错误。而命令行用户会犯很多错误,例如拼写错误,命令的错误,语法的复杂性使得用户如果没有练习的话很容易忘记。这些错误则增加了运行的时间。

易学性

语法

命令行有许多的语法,这使得它非常难以学习。

视觉语言,隐喻

视觉语言可能也需要用户的学习,这也取决于文化的特点。使用隐喻可能也需要

一些学习。

6 列出成功的虚拟环境所依赖的技术。

•视觉显示
•头部位置传感
•手部位置传感
•手持操作
•力反馈和触觉
•声音输入和输出
•其他感觉
•协作和竞争虚拟环境

chapter 6

1 设计一个触摸屏音乐点唱机,允许用户从菜单中选择本周最受欢迎的五首歌曲。绘制以下菜单类型的界面示意图:二进制菜单、多项菜单、复选框、下拉菜单。争论哪种设计最适合用户

留给你自己想象吧。这是一个理想的家庭作业,与学生团队一起完成。在开始这次冒险之前,聪明的学生将阅读课文的第6.2节到第6.4节(或者必须承认是整个第6章)。另外,看看在现有系统(比如iTunes网站)上是如何实现的,也是值得花时间的。通常情况下(在可用性测试之后),菜单类型的组合可能是最好的答案。

2.你要负责设计一个导航数字图书馆中1250本书的菜单树。提出一个关于菜单应该有更大的深度(关卡数量)还是广度(每个关卡的项目数量)的争论。

检查菜单深度和广度的最好方法是先看极端情况。一份包含1250本书的菜单会有多麻烦?拥有1250个1级深度菜单会有多尴尬?一种自然的分类方法是将这些条目按“小尺寸”分类,例如杜威十进制分类法,例如科学类书籍、小说类书籍、非小说类书籍等等。当然,回想一下,如果你正在建立一个图书网站,你可能想以一种可能促进销售的方式对图书进行分类,例如,出于商业原因,不管图书的分类是什么,你可能想把它们分类为十大畅销书。这很容易在课堂上通过白板上的菜单树图进行讨论,便于编辑。这位作者经常强调,没有100%正确的答案,但健康的设计讨论与可用性测试将导致首选的解决方案。

1 一个基于电话的菜单系统正在为一个杂志订阅服务系统设计。可供选择的杂志有7种:《国家地理》、《旅行与休闲》、《企业家》、《时代》、《高尔夫》、《美国新闻》和《华尔街日报》《世界报告》和《财富》杂志。描述语音菜单的三种合理的顺序,并对每一种进行说明。

设计语音菜单的原则:1)声频菜单通过讲述让用户获知,用户通过声音给出恢

复或者按键回复;2)声频菜单具有不易记忆的特征;3)避免复杂的菜单结构

和良好的退出机制

电话杂志订阅系统使用的菜单结构是树状,树状菜单常是以类别划分的、而且检索迅速、可扩展又不容易破坏它的完整性。

菜单的具体结构是:

顶层菜单:杂志订阅、订阅查询、重听、退出;

杂志订阅选项下面是订阅的杂志名称的排序,选择对于的杂志之后,就会提示输入订阅的数量和买家信息和最后的确认订单等,成功就会生成订单号,不成功就会返回到顶层菜单;

订单查询进去会提示输入订单号和证件验证信息等,输入成功就会报出订单信

息,三次输入错误就会提示错误后自动挂断;

其中杂志名称的排序主要可以采用三种设计的排法:

1、按首字母(可以设置成Entrepreneur, Fortune, Golf, National Geographic, Time, Travel and Leisure, US News and World Report):这个排序方法较为经典、共7个杂志,可以设计7个按键选择,优点在于简单稳定,不怕用户难易记忆,维护简单,但是缺电却是可扩展性差。

2、按销量(按实际销量由高到低排序):这是个人性化的排序方法,较为畅销的

杂志往往排在第一位,方便了大多数人的选择,但这同时伴随着缺点,因为可能销量经常在变化,常使用的用户就会发现菜单经常改变而变得不安。

3、按类别(可以分成科技自然、人文和生活三类:科技自然包括Entrepreneur,

National Geographic;人文包括Time、US News and World Report, Fortune;生活包括:Travel and Leisure, Golf)按类别设计的菜单使得每层菜单变得更简洁容易

记忆,但是增加的一层菜单会使得总菜单结构和退出机制变得复杂。

2 提供三个应用程序示例,其中菜单选择和表单填充比直接操作策略更合适。

•只需很少或不需要培训的系统,例如从自动柜员机中提取金额,如20美元、40美元、60美元等。

•任务驱动应用,例如,一个自动点餐菜单,你可以从一个固定的选择列表中从饮料、开胃菜、主菜、甜点等

•简单的确认语句,例如删除文件Yes/No?

3 如果为了完成一个简单的任务而必须选择多个菜单,经常使用菜单的用户可能会感到厌烦。建议两种方法,您可以细化菜单的方法,以适应专家或经常用户。

键盘快捷键会有所帮助,可以使用减少的击键,例如特殊的组合键(例如Cntl-S),以加速导航。另一种方法是使用导航辅助工具(一些非正式的称呼它为“面包屑”)来轻松直接地导航到菜单位置,而不需要一步一步地在菜单树上走或走下。

4 当用户在菜单结构中导航时,他们可能会迷失方向。作者提出了一些技术来帮助缓解这种方向感障碍,比如在菜单中显示当前位置。画一个草图,你如何可以显示用户的位置,一个在线汽车展厅,假设用户浏览了以下路径:
主菜单>中型汽车>本田>雅阁

导航辅助工具可以帮助指导用户完成简单和复杂的菜单。突出显示菜单位置就是一个例子。在上面的例子中,你可以通过以下方法来突出你在“中型汽车”展示中的位置:

主菜单>中型汽车>本田>雅阁

chapter 7

1.分析一个你经常使用的应用程序,比如你的文字处理器,并列出各种通过键盘快捷键可用的命令(例如CTRL-P打印,F7拼写检查,等等)。确定这些命令是否为用户设计得很好。

几乎任何关于这个问题的讨论,只要有合理的理由,都是可以接受的。大多数图形用户界面中的键盘快捷键成为经验丰富的用户的一种命令菜单。“Windows XP”表示菜单中“_”的单字母命令快捷方式,支持用户通过键盘命令执行所有操作。由于命令行仍然是许多移动设备上的规范,这不是一个可以忽略的主题。而且,由于人们喜欢使用“表情符号”和短信简写(如“How R U?”),这个讨论问题可能会引发情感上的争论。

2.对自然语言交互(NLI)在用户界面中应该扮演什么角色进行深思熟虑的讨论。一定要列出至少三个NLI的好处和限制。

支持的理由包括它的自然语言,比如初学者的外观,人工智能应用程序,例如代理和专家系统,或者那些具有普遍可用性限制的,可以享受自然语言的打字和语音识别能力的功能。
反对的理由包括:用户需求有限,因此可用工具的集合也有限;可居住性(用户决定每个应用程序适合哪些操作和对象);许多人喜欢更可视化(更简洁)的用户界面,比如直接操作应用程序。

3.列出几种命令语言对用户有吸引力的情况。

当预期系统会频繁使用,用户对任务和界面概念非常了解,屏幕空间非常宝贵(例如许多手机),响应时间和显示速度较慢,并且许多功能可以在紧凑的表达式中组合在一起时,命令语言可能很有吸引力。用户必须学习语义和语法,但他们可以发起,而不仅仅是响应,并可以快速指定涉及多个对象和选项的操作。最后,可以很容易地指定和存储复杂的命令序列,以便将来作为宏使用。

chapter 8

1.一些设计师建议在电话菜单系统中使用语音识别。这将允许用户通过语音与系统交互,而不是按拨号板上的按钮。给出两个支持和三个反对建议的理由。

支持的理由包括普遍可用性和操作手机所需的有限词汇

反对的理由包括:鉴于当前的技术,错误率、压力条件(例如,在紧急情况下打电话求助还是打电话给朋友)会改变你的语言,背景噪音的影响

2.一家公司正在设计一个可以在公共场所显示天气信息的信息亭。kiosk将有一个触摸屏,用户可以在地图上选择城市。给出三个原因,为什么触摸屏是一个有效的设备这个应用程序。

不需要外部的指针装置(如鼠标),真正的人机界面只有电脑屏幕,电脑、电源等隐藏和保护在kiosk容器内,界面设计直观,无需培训。

3.解释直接控制和间接控制指向设备之间的区别。当一种类型比另一种类型更适合时,命名任务。

直接控制的例子是我们在直接操作应用程序中的手指,如上面问题#2中的kiosk。PDA上的触控笔是另一种直接控制设备。间接控制是一个鼠标连接到您的工作站。鼠标提高了指向任务的精度,它的成本低,使用广泛。

4.举例说明什么时候语音生成比视觉屏幕更能满足用户的需求。但是,也要提供用户使用语音生成器时可能会有的限制列表。

语音生成和识别可以提高对那些有物理挑战的人的通用可用性。每个设计师都必须应对语音输出的三个障碍:与视觉显示相比,语音输出的速度较慢,语音的短暂性,以及扫描/搜索的难度。见下文框8.2:

图片1

5.在过去的几年中,移动设备的使用一直在快速增长。这些设备的屏幕通常比台式电脑小得多。列出两种策略,如何成功地实现在小型设备上浏览用户的一组数码照片的界面。

快速串行视觉呈现(RSVP)也可以提高小屏幕上的阅读能力,它以恒定的速度或与内容相适应的速度动态呈现文本。

对于线性阅读来说,将数据直接迁移到长、可滚动的显示中是可以接受的,但是这会使文档中的比较变得困难。数据修改包括汇总文本或创建更小的图片。提供对文档所有部分的快速访问并允许用户请求更多信息是有效的。数据抑制可以通过消除文档的各个部分,或选择性地对单词进行抽样来实现。最后,使用可视化技术的紧凑概述(第14章)可以提供对所有原始信息的访问。

6.给出上下文感知计算的定义。提供一个上下文感知的应用程序示例 能够满足游客用户需求的计算

通过感知用户和环境的状态,自动支持用户的行为

各种传感器可捕获用户的手势、表情等,据此预测用户的需要,并作出相应的反应

如,眼球追踪设备通过检测用户的视线活动实现自动导航

基于电影海报图像的影片标签生成任务

付碧波 王仲煜

源代码:https://paste.ubuntu.com/p/RPdTG3yZJk/

任务分析

zidan

电影类型可分为动作、喜剧、恐怖、冒险、爱情、警匪、科幻、战争、灾难、悬疑、西部等等

所以,根据电影海报生成标签,是一个多标签图像分类问题

环境配置

2021 06 20 16 54 51

tensorflow_gpu 2.0

Keras 2.3.1

tensorflow-hub 0.6.0

加载和处理数据集

读取csv文件(MovieGenre.csv)

2021 06 19 18 39 30

2021 06 19 18 38 09

下载图片(数据集)

2021 06 19 18 42 46

download_parallel函数定义在utils.py中

持久化

2021 06 19 18 43 03

标签频率可视化

2021 06 19 18 49 41

222

清除低频标签

并非所有电影流派都以相同数量表示。其中一些可能很少出现。忽略少于1000个观察值的所有标签(简短,西方,音乐,体育)。这意味着由于缺少对这些标签的观察,因此不会训练该模型预测这些标签。

2021 06 19 18 52 58

搭建模型

2021 06 19 20 14 06

划分数据集

将建模数据拆分为训练和验证在机器学习实践中很常见。 将分配 80% 的图像用于训练,20% 用于验证。

2021 06 19 20 27 35

y二值化

我们需要目标是一个字符串列表,以适合二值化器(多热编码)。

2021 06 19 20 30 49

一些案例

2

label encoding

最初的目标是人类可以轻松理解的字符串列表。但是,如果我们要构建和训练神经网络,我们需要创建二进制标签(多热编码)。
这对于多标签分类至关重要。为了二值化我们的标签,我们将使用 scikit-learn 的 MultiLabelBinarizer。

2021 06 19 20 38 58

2021 06 19 20 40 35

建立快速输入管道

首先需要编写一些函数来解析图像文件并生成一个表示特征的张量和一个表示标签的张量。在这个函数中,可以调整图像的大小以适应模型期望的输入。还可以对像素值进行归一化 介于 0 和 1 之间。这是有助于加速训练收敛的常见做法。如果我们将每个像素视为一个特征,我们希望这些特征具有相似的范围,以便梯度不会超出 并且我们只需要一个全局学习率乘数。

2021 06 20 11 56 36

要在数据集上训练模型,希望数据为:

  • Well shuffled,使数据满足独立同分布
  • Batched 分批
  • Batches to be available as soon as possible.,批量尽快供货。

2021 06 20 12 05 02

AUTOTUNE 动态适配预处理和预取

创建一个为 TensorFlow 生成训练和验证数据集的函数。

2021 06 20 14 34 38

每个批次将是一对数组(一个保存特征,另一个保存标签)。
特征数组的形状为 (BATCH_SIZE, IMG_SIZE, IMG_SIZE, CHANNELS)。
标签数组的形状为 (BATCH_SIZE, N_LABELS),其中 N_LABELS 是标签的最大数量。
让我们通过分析第一批来验证这些数组的形状:

2021 06 20 14 38 07

Model Building

TF.hub

一个模型仓库,在这里你可以获取到任何你想要的模型,并且用它来做transfer learning;准确来说,tensorflow hub是一个包,包含了一些下载的操作,直接帮你把模型搞下来。

使用TF.Hub迁移学习

我们将在称为迁移学习的过程中使用预训练模型,而不是从头开始构建和训练新模型。大多数用于视觉应用的预训练模型都是在ImageNet 上训练的,ImageNet 是一个拥有超过 1400 万个图像的大型图像数据库 图像分为 2 万多个类别。 迁移学习背后的想法是,由于这些模型是在大型和一般分类任务的背景下训练的,因此可以通过提取和迁移先前学习的有意义的特征来处理更具体的任务。 我们需要做的就是获取一个预先训练好的模型,并在其上简单地添加一个新的分类器。 新的分类头将从头开始训练,以便我们将目标重新用于我们的多标签分类任务。

来自tfhub.dev的任何与Tensorflow 2兼容的图像特征矢量URL都可能对数据集很有趣。唯一的条件是确保准备的数据集中图像特征的形状与要重用的模型的预期输入形状相匹配。

首先,准备特征提取器。将使用MobileNet V2的预训练实例,其深度乘数为1.0,输入大小为224x224。实际上,MobileNet V2是一大类神经网络体系结构,其主要目的是加快设备上的推理速度。它们的大小不同,具体取决于深度乘数(隐藏的卷积层中的要素数量)和输入图像的大小。

2021 06 20 14 45 34

特征提取器接受形状为 (224, 224, 3) 的图像并为每个图像返回一个 1280 长度的向量。

我们应该冻结特征提取层中的变量,以便训练只修改新的分类层。
通常,在处理与特征提取器训练的原始数据集相比非常小的数据集时,这是一个很好的做法。

2021 06 20 14 48 00

Sequential

现在,可以将特征提取器层包装在tf.keras.Sequential模型中,并在顶部添加新层。

2021 06 20 14 52 00

需要在最终的神经元中应用S型激活函数,以计算出每种流派的概率得分。这样就可以依靠多个逻辑回归在同一模型中同时进行训练。每个最终神经元将充当一个单一类别的单独的二进制分类器,即使提取的特征对于所有最终神经元而言都是相同的。

使用此模型生成预测时,应该期望每个流派都有一个独立的概率得分,并且所有概率得分不一定总和为1。这与在多类分类中使用softmax层(其中概率得分的总和)不同。输出等于1。

2021 06 20 14 58 31

训练模型

损失函数

在前面的步骤中,我们准备了数据集并通过在预训练网络(无输出)之上附加多标签神经网络分类器来构建模型。我们现在可以继续训练我们的模型,但我们需要定义两个主要功能:

损失函数:我们需要它来测量训练批次的模型误差(成本)。它必须是可微的,以便反向传播神经网络中的误差并更新权重。

评估函数:它应该代表我们真正关心的最终评估指标。与损失函数不同,它必须更直观地了解模型在现实世界中的表现。
这个笔记本的目的不仅是分享多标签分类的一般设计,而且是研究定制损失函数的选择以直接优化我们关心的评估指标的优势。

F1-score

https://blog.csdn.net/qq_14997473/article/details/82684300

https://zhuanlan.zhihu.com/p/64315175

假设我们需要 F1-score来评估模型在每个标签上的性能。 F1-score是 Precision 和 Recall 的调和平均值。并且,在考虑任何特定标签时,Precision 和 Recall 的计算会考虑TP的数量、FP的数量和 FN 的数量。

confusion matrix

我们计算与标签总数一样多的F1-score ,然后将它们平均以获得我们所说的宏观 F1-score 。如果它们在多标签分类任务中具有相同的重要性,则取所有标签的平均值是合理的。

F1-score 的问题在于它不可微,因此我们不能将其用作损失函数来计算梯度并在训练模型时更新权重。这是因为F1-score 需要测量二元预测 (0/1)。这些二元预测是通过对模型生成的概率分数应用决策阈值来获得的。例如,如果动作的概率高于阈值 0.5,我们可以预测 1(一部电影是关于动作的),否则我们预测 0(没有动作)。

通常我们使用二元交叉熵损失表示特定类别的观测值的负对数似然 -log(p),模型预测该类别的概率 p。一般来说,这种损失效果很好,并被广泛用于训练分类器,但它并不直接与我们想要最大化的 F1-score 相关联和对齐。

我们可以做的是修改 F1-score 以使其可微。我们可以通过使用概率而不应用任何阈值,将真阳性、假阳性、假阴性的数量计算为离散整数值,而不是将它们计算为似然值的连续总和。

我们将这个版本的 F1-score 称为 soft-F1-score。 下面,您可以看到在 TensorFlow 中的一批预测中实现它的代码。

这里有一些事情需要考虑:

每个标签的成本实际上是 1 - 该标签的 soft-F1。 如果我们想最大化 soft-F1,我们应该最小化 1 - soft-F1。
可以替换soft-F1定义中的Precision和Recall,得到更直接的基于TP、FP和FN的公式。 您要这样做的原因是因为当 TP = 0 时 F1 的调和平均表达式未定义,但已定义翻译表达式。 F1 = 2.TP / (2.TP + FN + FP)
一批观察的总成本将是所有标签的平均成本。 我们将其称为macro soft-F1 loss。
我们必须确保批量大小足够大,以便在训练时看到具有代表性的macro soft-F1 loss。

2021 06 20 15 22 17

接下来,我们将训练两个具有相同架构但有两个不同损失函数的模型:

第一个将使用macro soft-F1 loss进行训练。
第二个将使用二元交叉熵损失进行训练。
另一方面,我们可能会默认为两个模型考虑一个单一的评估指标:macro F1-score @ threshold 0.5。

2021 06 20 15 24 56

Train with macro soft-F1 loss

2021 06 20 16 10 15

2021 06 20 16 11 16

2021 06 20 16 12 20

2021 06 20 16 12 45

2021 06 20 16 18 10

2021 06 20 16 05 54

下载

Train with binary cross-entropy loss

2021 06 20 16 21 13

2021 06 20 16 21 35

2021 06 20 16 21 53

2021 06 20 16 06 27

3

到目前为止,我们已经训练了两个具有相同架构的神经网络模型。
第一个是直接针对macro F1 分数进行优化,而第二个则更经典,针对二元交叉熵进行了优化。
在这两种情况下,训练的模型在预测电影海报的类型时都会为每个标签生成一个独立的概率分数。
要创建最终决策系统,我们需要为每个标签选择一个介于 0 和 1 之间的决策阈值,以便将每个概率转换为二元信息。 通常系统的性能取决于这些决策阈值的选择。
因此,让我们根据我们为每个标签设置阈值的级别来检查系统在验证集上的行为。

utils 模块中有一个名为 perf_grid 的函数,可以帮助创建性能网格。
在性能网格中,每个标签的阈值以 0.01 的步长从 0 增加到 1。
对于每个阈值和每个标签,我们计算不同的度量(tp、fn、fp、精度、召回率、f1-score)。

2021 06 20 16 27 56

对于每个标签,都有一个阈值可以最大化使用二元交叉熵损失训练的第二个模型的性能。
使用具有 bce 损失的第二个模型时,哪些标签具有最高的最大性能?

2021 06 20 16 29 39

数据集中标签的频率与其实现的性能之间是否存在相关性?

相关性0.99

现在,比较两个不同模型的性能曲线。

10

2021 06 20 16 39 52

使用每个模型可视化标签概率值的直方图。

30

40

当使用二元交叉熵损失训练时,输出的概率分布具有一些高斯特性(注意蓝色直方图的钟形)。实际上,这种优化是从数据的原始分布中学习的。我们可以看到,对于覆盖数据集 50% 的标签“Drama”,概率分布以 0.5 为中心。顺便说一句,为“戏剧”构建的分类器似乎非常弱,因为这两个类似乎没有在概率值上分开。我们还可以注意到,标签越不频繁,分布就越向左移动。例如,“犯罪”的概率分数似乎非常低,而该标签仅覆盖了数据集的 14%。该模型从这种稀有性中学习以预测较低的概率值。另一方面,当使用Macro soft-F1 损失时,我们正在创建一个系统,它不会反映条件概率值的相同幅度。相反,它学会了减少犹豫并生成非常接近 1 或非常接近 0 的预测。我们在中间范围内的概率值较少。因此,在该范围内改变阈值时,性能不会发生太大变化。
使用Macro soft-F1 损失进行优化可以取代一些详尽的技术,例如:

搜索使验证集性能最大化的最佳决策阈值
通过在训练前对少数类进行过采样或对多数类进行欠采样来校准概率值(在多标签分类的情况下非常复杂)

测试

2021 06 20 16 48 10

55

72

74

导出模型

2021 06 20 16 51 37

2021 06 20 16 53 09

2021 06 20 16 53 26

链接: https://pan.baidu.com/s/140EP6PzfMVQz6AekyvxYGg 密码: 8pu8

软件架构

2021 06 15 11 39 38

2021 06 15 11 42 10

2021 06 15 14 34 08

2021 06 15 14 35 43

2021 06 15 14 38 50

2021 06 15 20 39 03

2021 06 15 14 58 10

2021 06 15 15 00 38

2021 06 15 15 01 56

2021 06 15 20 38 03

2021 06 15 20 36 26

2021 06 15 20 40 26

2021 06 15 20 41 54

逻辑视图(Logical View):也称为概念视图,主要关注系统的功能需求,即系统提供给最终用户的功能

开发视图(Development View):也称模块/包视图,是软件逻辑视图在开发实现上的设计视图,体现为以软件模块、包、库、子系统等
形式存在的软件开发结构化组织模型

进程视图(Process View):侧重于系统的运行特性,主要关注系统的非功能性的需求的满足,是系统的一种运行时(run-time)结构模型。

物理视图(Physical View):物理视图主要考虑如何把软件映射到硬件上,它通常要考虑软件系统在在计算物理节点与网络拓扑结构上
的运行部署等问题。主要关注系统性能、可扩展性、可靠性等软件非功能性需求约束

场景视图(Scenarios View):从系统使用的角度对系统结构的描述。它反映的是在完成一个系统功能时,系统各功能构件间的交互协作关系

2021 06 15 20 45 30

2021 06 15 20 55 25

设计模式

总原则:开闭原则

开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等,后面的具体设计中我们会提到这点。

1、单一职责原则

不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。

2、里氏替换原则

里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科

历史替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。

3、依赖倒转原则

这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。

4、接口隔离原则

这个原则的意思是:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。

5、迪米特法则

就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。

最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。

6、合成复用原则

原则是尽量首先使用合成/聚合的方式,而不是使用继承。

预先找出设计问题的变化点,使用统一的接口封装起来,如果未来发生变化的时候,可以通过接口扩展新的功能,而不需要去修改原来旧的实现。也可以把这个原则理解为OCP(Open-Close Principle, 开闭原则 ) 原则,也就是说一个软件实体应当对扩展开放,对修改关闭。在设计一个模块的时候,要保证这个模块可以在不需要被修改的前提下可以得到扩展。这样做的好处就是通过扩展给系统提供了新的职责,以满足新的需求,同时又没有改变系统原来的功能。

创建型模式:

创建对象的模式,对象实例化的设计
• 它帮助一个系统独立于如何创建、组合和表示它的那些对象(在创建型设计模式中统称为Product)
• 关注的是对象的创建,创建型模式将创建对象的过程进行了抽象,也可以理解为将创建对象的过程进行了封装,作为客户程序(Client)
仅仅需要去使用对象,而不再关心创建对象过程中的逻辑

单例模式*

5种实现方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Singleton {
private static Singleton instance;

private Singleton() {}

//提供一个静态的公有方法,当使用到该方法时,才去创建 instance
//即懒汉式
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//双端锁+volatile
public class SingletonTest09 {
private SingletonTest09(){}

private static volatile SingletonTest09 instace;

public static SingletonTest09 getInstance(){
if (instace==null){
synchronized (SingletonTest09.class){
if (instace==null){
instace=new SingletonTest09();
}
}
}

return instace;
}

}

除以上两种,还有饿汉式,静态内部类,枚举

原型模式

使用原型实例指定要创建对象的类型,通过复制这个原型来创建新对象。

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public abstract class Prototype {
abstract Prototype myClone();
}


public class ConcretePrototype extends Prototype {

private String filed;

public ConcretePrototype(String filed) {
this.filed = filed;
}

@Override
Prototype myClone() {
return new ConcretePrototype(filed);
}

@Override
public String toString() {
return filed;
}
}

public class Client {
public static void main(String[] args) {
Prototype prototype = new ConcretePrototype("abc");
Prototype clone = prototype.myClone();
System.out.println(clone.toString());
}
}
//abc

工厂方法模式*

定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化操作推迟到子类。

在简单工厂中,创建对象的是另一个类,而在工厂方法中,是由子类来创建对象。

下图中,Factory 有一个 doSomething() 方法,这个方法需要用到一个产品对象,这个产品对象由 factoryMethod() 方法创建。该方法是抽象的,需要由子类去实现。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public abstract class Factory {
abstract public Product factoryMethod();
public void doSomething() {
Product product = factoryMethod();
// do something with the product
}
}

public class ConcreteFactory extends Factory {
public Product factoryMethod() {
return new ConcreteProduct();
}
}

public class ConcreteFactory1 extends Factory {
public Product factoryMethod() {
return new ConcreteProduct1();
}
}

public class ConcreteFactory2 extends Factory {
public Product factoryMethod() {
return new ConcreteProduct2();
}
}

抽象工厂模式*

提供一个接口,用于创建 相关的对象家族

抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说必须一起创建出来。而工厂方法模式只是用于创建一个对象,这和抽象工厂模式有很大不同。

抽象工厂模式用到了工厂方法模式来创建单一对象,AbstractFactory 中的 createProductA() 和 createProductB() 方法都是让子类来实现,这两个方法单独来看就是在创建一个对象,这符合工厂方法模式的定义。

至于创建对象的家族这一概念是在 Client 体现,Client 要通过 AbstractFactory 同时调用两个方法来创建出两个对象,在这里这两个对象就有很大的相关性,Client 需要同时创建出这两个对象。

从高层次来看,抽象工厂使用了组合,即 Cilent 组合了 AbstractFactory,而工厂方法模式使用了继承。


1
2
public class AbstractProductA {
}
1
2
public class AbstractProductB {
}
1
2
public class ProductA1 extends AbstractProductA {
}
1
2
public class ProductA2 extends AbstractProductA {
}
1
2
public class ProductB1 extends AbstractProductB {
}
1
2
public class ProductB2 extends AbstractProductB {
}
1
2
3
4
public abstract class AbstractFactory {
abstract AbstractProductA createProductA();
abstract AbstractProductB createProductB();
}
1
2
3
4
5
6
7
8
9
public class ConcreteFactory1 extends AbstractFactory {
AbstractProductA createProductA() {
return new ProductA1();
}

AbstractProductB createProductB() {
return new ProductB1();
}
}
1
2
3
4
5
6
7
8
9
public class ConcreteFactory2 extends AbstractFactory {
AbstractProductA createProductA() {
return new ProductA2();
}

AbstractProductB createProductB() {
return new ProductB2();
}
}
1
2
3
4
5
6
7
8
public class Client {
public static void main(String[] args) {
AbstractFactory abstractFactory = new ConcreteFactory1();
AbstractProductA productA = abstractFactory.createProductA();
AbstractProductB productB = abstractFactory.createProductB();
// do something with productA and productB
}
}

抽象工厂模式中我们可以定义实现不止一个接口,一个工厂也可以生成不止一个产品类,抽象工厂模式较好的实现了“开放-封闭”原则,是工厂模式中较为抽象,并具一般性的模式。我们在使用中要注意使用抽象工厂模式的条件。

建造者模式*

v2 5a7bd484bf046798b86826e95ab894fa 720w

  1. 建造者模式(Builder Pattern) 又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象

  2. 建造者模式 是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class Computer {
private final String cpu;//必须
private final String ram;//必须
private final int usbCount;//可选
private final String keyboard;//可选
private final String display;//可选

private Computer(Builder builder){
this.cpu=builder.cpu;
this.ram=builder.ram;
this.usbCount=builder.usbCount;
this.keyboard=builder.keyboard;
this.display=builder.display;
}
public static class Builder{
private String cpu;//必须
private String ram;//必须
private int usbCount;//可选
private String keyboard;//可选
private String display;//可选

public Builder(String cup,String ram){
this.cpu=cup;
this.ram=ram;
}

public Builder setUsbCount(int usbCount) {
this.usbCount = usbCount;
return this;
}
public Builder setKeyboard(String keyboard) {
this.keyboard = keyboard;
return this;
}
public Builder setDisplay(String display) {
this.display = display;
return this;
}
public Computer build(){
return new Computer(this);
}
}
//省略getter方法
}
1
2
3
4
5
Computer computer=new Computer.Builder("因特尔","三星")
.setDisplay("三星24寸")
.setKeyboard("罗技")
.setUsbCount(2)
.build();

结构型模式

结构型模式是为解决怎样组装现有的类,设计他们的交互方式,从而达到实现一定的功能的目的
结构型模式包容了对很多问题的解决。例如:扩展性(外观、组成、代理、装饰)、封装性(适配器,桥接)

代理模式

控制对其它对象的访问。

代理有以下四类:

  • 远程代理(Remote Proxy):控制对远程对象(不同地址空间)的访问,它负责将请求及其参数进行编码,并向不同地址空间中的对象发送已经编码的请求。
  • 虚拟代理(Virtual Proxy):根据需要创建开销很大的对象,它可以缓存实体的附加信息,以便延迟对它的访问,例如在网站加载一个很大图片时,不能马上完成,可以用虚拟代理缓存图片的大小信息,然后生成一张临时图片代替原始图片。
  • 保护代理(Protection Proxy):按权限控制对象的访问,它负责检查调用者是否具有实现一个请求所必须的访问权限。
  • 智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作:记录对象的引用次数;当第一次引用一个对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。

Implementation

以下是一个虚拟代理的实现,模拟了图片延迟加载的情况下使用与图片大小相等的临时内容去替换原始图片,直到图片加载完成才将图片显示出来。

1
2
3
public interface Image {
void showImage();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class HighResolutionImage implements Image {

private URL imageURL;
private long startTime;
private int height;
private int width;

public int getHeight() {
return height;
}

public int getWidth() {
return width;
}

public HighResolutionImage(URL imageURL) {
this.imageURL = imageURL;
this.startTime = System.currentTimeMillis();
this.width = 600;
this.height = 600;
}

public boolean isLoad() {
// 模拟图片加载,延迟 3s 加载完成
long endTime = System.currentTimeMillis();
return endTime - startTime > 3000;
}

@Override
public void showImage() {
System.out.println("Real Image: " + imageURL);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ImageProxy implements Image {

private HighResolutionImage highResolutionImage;

public ImageProxy(HighResolutionImage highResolutionImage) {
this.highResolutionImage = highResolutionImage;
}

@Override
public void showImage() {
while (!highResolutionImage.isLoad()) {
try {
System.out.println("Temp Image: " + highResolutionImage.getWidth() + " " + highResolutionImage.getHeight());
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
highResolutionImage.showImage();
}
}
1
2
3
4
5
6
7
8
9
10
public class ImageViewer {

public static void main(String[] args) throws Exception {
String image = "http://image.jpg";
URL url = new URL(image);
HighResolutionImage highResolutionImage = new HighResolutionImage(url);
ImageProxy imageProxy = new ImageProxy(highResolutionImage);
imageProxy.showImage();
}
}

适配器模式*

把一个类接口转换成另一个用户需要的接口。


鸭子(Duck)和火鸡(Turkey)拥有不同的叫声,Duck 的叫声调用 quack() 方法,而 Turkey 调用 gobble() 方法。

要求将 Turkey 的 gobble() 方法适配成 Duck 的 quack() 方法,从而让火鸡冒充鸭子!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public interface Duck {
void quack();
}

public interface Turkey {
void gobble();
}

public class WildTurkey implements Turkey {
@Override
public void gobble() {
System.out.println("gobble!");
}
}

public class TurkeyAdapter implements Duck {
Turkey turkey;

public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}

@Override
public void quack() {
turkey.gobble();
}
}

public class Client {
public static void main(String[] args) {
Turkey turkey = new WildTurkey();
Duck duck = new TurkeyAdapter(turkey);
duck.quack();
}
}

桥接模式*

将抽象与实现分离开来,使它们可以独立变化。

  • Abstraction:定义抽象类的接口
  • Implementor:定义实现类接口

RemoteControl 表示遥控器,指代 Abstraction。

TV 表示电视,指代 Implementor。

桥接模式将遥控器和电视分离开来,从而可以独立改变遥控器或者电视的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
public abstract class TV {
public abstract void on();

public abstract void off();

public abstract void tuneChannel();
}

public class Sony extends TV {
@Override
public void on() {
System.out.println("Sony.on()");
}

@Override
public void off() {
System.out.println("Sony.off()");
}

@Override
public void tuneChannel() {
System.out.println("Sony.tuneChannel()");
}
}

public class RCA extends TV {
@Override
public void on() {
System.out.println("RCA.on()");
}

@Override
public void off() {
System.out.println("RCA.off()");
}

@Override
public void tuneChannel() {
System.out.println("RCA.tuneChannel()");
}
}

public abstract class RemoteControl {
protected TV tv;

public RemoteControl(TV tv) {
this.tv = tv;
}

public abstract void on();

public abstract void off();

public abstract void tuneChannel();
}

public class ConcreteRemoteControl1 extends RemoteControl {
public ConcreteRemoteControl1(TV tv) {
super(tv);
}

@Override
public void on() {
System.out.println("ConcreteRemoteControl1.on()");
tv.on();
}

@Override
public void off() {
System.out.println("ConcreteRemoteControl1.off()");
tv.off();
}

@Override
public void tuneChannel() {
System.out.println("ConcreteRemoteControl1.tuneChannel()");
tv.tuneChannel();
}
}

public class ConcreteRemoteControl2 extends RemoteControl {
public ConcreteRemoteControl2(TV tv) {
super(tv);
}

@Override
public void on() {
System.out.println("ConcreteRemoteControl2.on()");
tv.on();
}

@Override
public void off() {
System.out.println("ConcreteRemoteControl2.off()");
tv.off();
}

@Override
public void tuneChannel() {
System.out.println("ConcreteRemoteControl2.tuneChannel()");
tv.tuneChannel();
}
}

public class Client {
public static void main(String[] args) {
RemoteControl remoteControl1 = new ConcreteRemoteControl1(new RCA());
remoteControl1.on();
remoteControl1.off();
remoteControl1.tuneChannel();
RemoteControl remoteControl2 = new ConcreteRemoteControl2(new Sony());
remoteControl2.on();
remoteControl2.off();
remoteControl2.tuneChannel();
}
}

装饰模式

为对象动态添加功能。

装饰者(Decorator)和具体组件(ConcreteComponent)都继承自组件(Component),具体组件的方法实现不需要依赖于其它对象,而装饰者组合了一个组件,这样它可以装饰其它装饰者或者具体组件。所谓装饰,就是把这个装饰者套在被装饰者之上,从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的,这属于它的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能。可以看到,具体组件应当是装饰层次的最低层,因为只有具体组件的方法实现不需要依赖于其它对象。


设计不同种类的饮料,饮料可以添加配料,比如可以添加牛奶,并且支持动态添加新配料。每增加一种配料,该饮料的价格就会增加,要求计算一种饮料的价格。

下图表示在 DarkRoast 饮料上新增新添加 Mocha 配料,之后又添加了 Whip 配料。DarkRoast 被 Mocha 包裹,Mocha 又被 Whip 包裹。它们都继承自相同父类,都有 cost() 方法,外层类的 cost() 方法调用了内层类的 cost() 方法。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public interface Beverage {
double cost();
}

public class DarkRoast implements Beverage {
@Override
public double cost() {
return 1;
}
}

public class HouseBlend implements Beverage {
@Override
public double cost() {
return 1;
}
}

public abstract class CondimentDecorator implements Beverage {
protected Beverage beverage;
}

public class Milk extends CondimentDecorator {

public Milk(Beverage beverage) {
this.beverage = beverage;
}

@Override
public double cost() {
return 1 + beverage.cost();
}
}

public class Mocha extends CondimentDecorator {

public Mocha(Beverage beverage) {
this.beverage = beverage;
}

@Override
public double cost() {
return 1 + beverage.cost();
}
}

public class Client {

public static void main(String[] args) {
Beverage beverage = new HouseBlend();
beverage = new Mocha(beverage);
beverage = new Milk(beverage);
System.out.println(beverage.cost());
}
}

类应该对扩展开放,对修改关闭:也就是添加新功能时不需要修改代码。饮料可以动态添加新的配料,而不需要去修改饮料的代码。

不可能把所有的类设计成都满足这一原则,应当把该原则应用于最有可能发生改变的地方。

外观模式

提供了一个统一的接口,用来访问子系统中的一群接口,从而让子系统更容易使用。


观看电影需要操作很多电器,使用外观模式实现一键看电影功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class SubSystem {
public void turnOnTV() {
System.out.println("turnOnTV()");
}

public void setCD(String cd) {
System.out.println("setCD( " + cd + " )");
}

public void startWatching(){
System.out.println("startWatching()");
}
}

public class Facade {
private SubSystem subSystem = new SubSystem();

public void watchMovie() {
subSystem.turnOnTV();
subSystem.setCD("a movie");
subSystem.startWatching();
}
}

public class Client {
public static void main(String[] args) {
Facade facade = new Facade();
facade.watchMovie();
}
}

只和你的密友谈话。也就是说客户对象所需要交互的对象应当尽可能少。

享元模式

利用共享的方式来支持大量细粒度的对象,这些对象一部分内部状态是相同的。

  • Flyweight:享元对象
  • IntrinsicState:内部状态,享元对象共享内部状态
  • ExtrinsicState:外部状态,每个享元对象的外部状态不同

1
2
3
public interface Flyweight {
void doOperation(String extrinsicState);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ConcreteFlyweight implements Flyweight {

private String intrinsicState;

public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}

@Override
public void doOperation(String extrinsicState) {
System.out.println("Object address: " + System.identityHashCode(this));
System.out.println("IntrinsicState: " + intrinsicState);
System.out.println("ExtrinsicState: " + extrinsicState);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class FlyweightFactory {

private HashMap<String, Flyweight> flyweights = new HashMap<>();

Flyweight getFlyweight(String intrinsicState) {
if (!flyweights.containsKey(intrinsicState)) {
Flyweight flyweight = new ConcreteFlyweight(intrinsicState);
flyweights.put(intrinsicState, flyweight);
}
return flyweights.get(intrinsicState);
}
}
1
2
3
4
5
6
7
8
9
10
public class Client {

public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
Flyweight flyweight1 = factory.getFlyweight("aa");
Flyweight flyweight2 = factory.getFlyweight("aa");
flyweight1.doOperation("x");
flyweight2.doOperation("y");
}
}
1
2
3
4
5
6
Object address: 1163157884
IntrinsicState: aa
ExtrinsicState: x
Object address: 1163157884
IntrinsicState: aa
ExtrinsicState: y

组合模式

将对象组合成树形结构来表示“整体/部分”层次关系,允许用户以相同的方式处理单独对象和组合对象。

组件(Component)类是组合类(Composite)和叶子类(Leaf)的父类,可以把组合类看成是树的中间节点。

组合对象拥有一个或者多个组件对象,因此组合对象的操作可以委托给组件对象去处理,而组件对象可以是另一个组合对象或者叶子对象。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public abstract class Component {
protected String name;

public Component(String name) {
this.name = name;
}

public void print() {
print(0);
}

abstract void print(int level);

abstract public void add(Component component);

abstract public void remove(Component component);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Composite extends Component {

private List<Component> child;

public Composite(String name) {
super(name);
child = new ArrayList<>();
}

@Override
void print(int level) {
for (int i = 0; i < level; i++) {
System.out.print("--");
}
System.out.println("Composite:" + name);
for (Component component : child) {
component.print(level + 1);
}
}

@Override
public void add(Component component) {
child.add(component);
}

@Override
public void remove(Component component) {
child.remove(component);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Leaf extends Component {
public Leaf(String name) {
super(name);
}

@Override
void print(int level) {
for (int i = 0; i < level; i++) {
System.out.print("--");
}
System.out.println("left:" + name);
}

@Override
public void add(Component component) {
throw new UnsupportedOperationException(); // 牺牲透明性换取单一职责原则,这样就不用考虑是叶子节点还是组合节点
}

@Override
public void remove(Component component) {
throw new UnsupportedOperationException();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Client {
public static void main(String[] args) {
Composite root = new Composite("root");
Component node1 = new Leaf("1");
Component node2 = new Composite("2");
Component node3 = new Leaf("3");
root.add(node1);
root.add(node2);
root.add(node3);
Component node21 = new Leaf("21");
Component node22 = new Composite("22");
node2.add(node21);
node2.add(node22);
Component node221 = new Leaf("221");
node22.add(node221);
root.print();
}
}
1
2
3
4
5
6
7
Composite:root
--left:1
--Composite:2
----left:21
----Composite:22
------left:221
--left:3

行为型模式

策略模式

定义一系列算法,封装每个算法,并使它们可以互换。

策略模式可以让算法独立于使用它的客户端。

  • Strategy 接口定义了一个算法族,它们都实现了 behavior() 方法。
  • Context 是使用到该算法族的类,其中的 doSomething() 方法会调用 behavior(),setStrategy(Strategy) 方法可以动态地改变 strategy 对象,也就是说能动态地改变 Context 所使用的算法。

与状态模式的比较

状态模式的类图和策略模式类似,并且都是能够动态改变对象的行为。但是状态模式是通过状态转移来改变 Context 所组合的 State 对象,而策略模式是通过 Context 本身的决策来改变组合的 Strategy 对象。所谓的状态转移,是指 Context 在运行过程中由于一些条件发生改变而使得 State 对象发生改变,注意必须要是在运行过程中。

状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 Context 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 使用的算法。

设计一个鸭子,它可以动态地改变叫声。这里的算法族是鸭子的叫声行为。

1
2
3
public interface QuackBehavior {
void quack();
}
1
2
3
4
5
6
public class Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("quack!");
}
}
1
2
3
4
5
6
public class Squeak implements QuackBehavior{
@Override
public void quack() {
System.out.println("squeak!");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Duck {

private QuackBehavior quackBehavior;

public void performQuack() {
if (quackBehavior != null) {
quackBehavior.quack();
}
}

public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
}
1
2
3
4
5
6
7
8
9
10
public class Client {

public static void main(String[] args) {
Duck duck = new Duck();
duck.setQuackBehavior(new Squeak());
duck.performQuack();
duck.setQuackBehavior(new Quack());
duck.performQuack();
}
}
1
2
squeak!
quack!

模板方法模式

观察者模式*

定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知并且自动更新状态。

主题(Subject)是被观察的对象,而其所有依赖者(Observer)称为观察者。


主题(Subject)具有注册和移除观察者、并通知所有观察者的功能,主题是通过维护一张观察者列表来实现这些操作的。

观察者(Observer)的注册功能需要调用主题的 registerObserver() 方法。


天气数据布告板会在天气信息发生改变时更新其内容,布告板有多个,并且在将来会继续增加。


1
2
3
4
5
6
7
public interface Subject {
void registerObserver(Observer o);

void removeObserver(Observer o);

void notifyObserver();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class WeatherData implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;

public WeatherData() {
observers = new ArrayList<>();
}

public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
notifyObserver();
}

@Override
public void registerObserver(Observer o) {
observers.add(o);
}

@Override
public void removeObserver(Observer o) {
int i = observers.indexOf(o);
if (i >= 0) {
observers.remove(i);
}
}

@Override
public void notifyObserver() {
for (Observer o : observers) {
o.update(temperature, humidity, pressure);
}
}
}
1
2
3
public interface Observer {
void update(float temp, float humidity, float pressure);
}
1
2
3
4
5
6
7
8
9
10
11
public class StatisticsDisplay implements Observer {

public StatisticsDisplay(Subject weatherData) {
weatherData.registerObserver(this);
}

@Override
public void update(float temp, float humidity, float pressure) {
System.out.println("StatisticsDisplay.update: " + temp + " " + humidity + " " + pressure);
}
}
1
2
3
4
5
6
7
8
9
10
11
public class CurrentConditionsDisplay implements Observer {

public CurrentConditionsDisplay(Subject weatherData) {
weatherData.registerObserver(this);
}

@Override
public void update(float temp, float humidity, float pressure) {
System.out.println("CurrentConditionsDisplay.update: " + temp + " " + humidity + " " + pressure);
}
}
1
2
3
4
5
6
7
8
9
10
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);

weatherData.setMeasurements(0, 0, 0);
weatherData.setMeasurements(1, 1, 1);
}
}
1
2
3
4
CurrentConditionsDisplay.update: 0.0 0.0 0.0
StatisticsDisplay.update: 0.0 0.0 0.0
CurrentConditionsDisplay.update: 1.0 1.0 1.0
StatisticsDisplay.update: 1.0 1.0 1.0

迭代器模式

提供一种顺序访问聚合对象元素的方法,并且不暴露聚合对象的内部表示。

  • Aggregate 是聚合类,其中 createIterator() 方法可以产生一个 Iterator;
  • Iterator 主要定义了 hasNext() 和 next() 方法;
  • Client 组合了 Aggregate,为了迭代遍历 Aggregate,也需要组合 Iterator。
image-20191130164425351

1
2
3
public interface Aggregate {
Iterator createIterator();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ConcreteAggregate implements Aggregate {

private Integer[] items;

public ConcreteAggregate() {
items = new Integer[10];
for (int i = 0; i < items.length; i++) {
items[i] = i;
}
}

@Override
public Iterator createIterator() {
return new ConcreteIterator<Integer>(items);
}
}
1
2
3
4
5
6
public interface Iterator<Item> {

Item next();

boolean hasNext();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ConcreteIterator<Item> implements Iterator {

private Item[] items;
private int position = 0;

public ConcreteIterator(Item[] items) {
this.items = items;
}

@Override
public Object next() {
return items[position++];
}

@Override
public boolean hasNext() {
return position < items.length;
}
}
1
2
3
4
5
6
7
8
9
10
public class Client {

public static void main(String[] args) {
Aggregate aggregate = new ConcreteAggregate();
Iterator<Integer> iterator = aggregate.createIterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}

责任链模式

命令模式*

将命令封装成对象中,具有以下作用:

  • 使用命令来参数化其它对象

  • 将命令放入队列中进行排队

  • 将命令的操作记录到日志中

  • 支持可撤销的操作

  • Command:命令

  • Receiver:命令接收者,也就是命令真正的执行者

  • Invoker:通过它来调用命令

  • Client:可以设置命令与命令的接收者


设计一个遥控器,可以控制电灯开关。


1
2
3
public interface Command {
void execute();
}
1
2
3
4
5
6
7
8
9
10
11
12
public class LightOnCommand implements Command {
Light light;

public LightOnCommand(Light light) {
this.light = light;
}

@Override
public void execute() {
light.on();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class LightOffCommand implements Command {
Light light;

public LightOffCommand(Light light) {
this.light = light;
}

@Override
public void execute() {
light.off();
}
}
1
2
3
4
5
6
7
8
9
10
public class Light {

public void on() {
System.out.println("Light is on!");
}

public void off() {
System.out.println("Light is off!");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 遥控器
*/
public class Invoker {
private Command[] onCommands;
private Command[] offCommands;
private final int slotNum = 7;

public Invoker() {
this.onCommands = new Command[slotNum];
this.offCommands = new Command[slotNum];
}

public void setOnCommand(Command command, int slot) {
onCommands[slot] = command;
}

public void setOffCommand(Command command, int slot) {
offCommands[slot] = command;
}

public void onButtonWasPushed(int slot) {
onCommands[slot].execute();
}

public void offButtonWasPushed(int slot) {
offCommands[slot].execute();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class Client {
public static void main(String[] args) {
Invoker invoker = new Invoker();
Light light = new Light();
Command lightOnCommand = new LightOnCommand(light);
Command lightOffCommand = new LightOffCommand(light);
invoker.setOnCommand(lightOnCommand, 0);
invoker.setOffCommand(lightOffCommand, 0);
invoker.onButtonWasPushed(0);
invoker.offButtonWasPushed(0);
}
}

备忘录模式

状态模式

访问者模式

为一个对象结构(比如组合结构)增加新能力。

  • Visitor:访问者,为每一个 ConcreteElement 声明一个 visit 操作
  • ConcreteVisitor:具体访问者,存储遍历过程中的累计结果
  • ObjectStructure:对象结构,可以是组合结构,或者是一个集合。

1
2
3
public interface Element {
void accept(Visitor visitor);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CustomerGroup {

private List<Customer> customers = new ArrayList<>();

void accept(Visitor visitor) {
for (Customer customer : customers) {
customer.accept(visitor);
}
}

void addCustomer(Customer customer) {
customers.add(customer);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Customer implements Element {

private String name;
private List<Order> orders = new ArrayList<>();

Customer(String name) {
this.name = name;
}

String getName() {
return name;
}

void addOrder(Order order) {
orders.add(order);
}

public void accept(Visitor visitor) {
visitor.visit(this);
for (Order order : orders) {
order.accept(visitor);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Order implements Element {

private String name;
private List<Item> items = new ArrayList();

Order(String name) {
this.name = name;
}

Order(String name, String itemName) {
this.name = name;
this.addItem(new Item(itemName));
}

String getName() {
return name;
}

void addItem(Item item) {
items.add(item);
}

public void accept(Visitor visitor) {
visitor.visit(this);

for (Item item : items) {
item.accept(visitor);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Item implements Element {

private String name;

Item(String name) {
this.name = name;
}

String getName() {
return name;
}

public void accept(Visitor visitor) {
visitor.visit(this);
}
}
1
2
3
4
5
6
7
public interface Visitor {
void visit(Customer customer);

void visit(Order order);

void visit(Item item);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class GeneralReport implements Visitor {

private int customersNo;
private int ordersNo;
private int itemsNo;

public void visit(Customer customer) {
System.out.println(customer.getName());
customersNo++;
}

public void visit(Order order) {
System.out.println(order.getName());
ordersNo++;
}

public void visit(Item item) {
System.out.println(item.getName());
itemsNo++;
}

public void displayResults() {
System.out.println("Number of customers: " + customersNo);
System.out.println("Number of orders: " + ordersNo);
System.out.println("Number of items: " + itemsNo);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Client {
public static void main(String[] args) {
Customer customer1 = new Customer("customer1");
customer1.addOrder(new Order("order1", "item1"));
customer1.addOrder(new Order("order2", "item1"));
customer1.addOrder(new Order("order3", "item1"));

Order order = new Order("order_a");
order.addItem(new Item("item_a1"));
order.addItem(new Item("item_a2"));
order.addItem(new Item("item_a3"));
Customer customer2 = new Customer("customer2");
customer2.addOrder(order);

CustomerGroup customers = new CustomerGroup();
customers.addCustomer(customer1);
customers.addCustomer(customer2);

GeneralReport visitor = new GeneralReport();
customers.accept(visitor);
visitor.displayResults();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
customer1
order1
item1
order2
item1
order3
item1
customer2
order_a
item_a1
item_a2
item_a3
Number of customers: 2
Number of orders: 4
Number of items: 6

中介者模式

解释器模式

链接: https://pan.baidu.com/s/1BWtrXszBZuQsIQbsdVfLDw 密码: f5bo

软件测试定义及基本概念

测试是为了证明程序有错,通过运行程序发现其中存在的问题。
• 软件测试可以证明软件有错;
• 软件测试不可以证明软件没有错

使用人工或者自动手段来运行或测试某个系统的过程,其目的在于检验它是否满足规定的需求或弄清预期结果与实际结果之间的差别

缺陷在软件开发每个阶段都会产生,所以软件测试应该贯穿软件开发始终

Debug不属于测试,属于开发调试

Fault, Error & Failure

Fault的定义:软件中的静态缺陷,可能导致系统或功能失效的异常条件(Abnormal condition that can cause an element or an item tofail.),可译为“故障”。

Error的定义:错误的内部状态是某些故障的表现,计算、观察或测量值或条件,与真实、规定或理论上正确的值或条件之间的差异(Discrepancy between a computed, observed or measured value or condition and the true, specified, or theoretically correct value or condition.),可译为“错误”。Error是能够导致系统出现Failure的系统内部状态。

Failure的定义:关于预期行为的要求或其他描述的外部不正确行为,当一个系统不能执行所要求的功能时,即为Failure,可译为“失效”。(Termination of the ability of an element or an item to perform a function as required.)

 举例

  病人告诉医生自己的各种症状,身体没有健康地工作 – Failures

  医生想找出疾病的根源,如病毒 – Fault

  病人身体状态异常,比如血压较高,心跳不规律等 – Errors

2021 05 23 14 23 02屏幕截图

2021 05 23 14 23 17屏幕截图

2021 05 23 14 23 32屏幕截图

2021 05 23 14 24 01屏幕截图

2021 05 23 14 24 42屏幕截图


2021 05 23 14 30 37屏幕截图

Fault is defined by fixing

2021 05 23 14 31 32屏幕截图

Fault is defined by testing!

2021 05 23 14 31 53屏幕截图

2021 05 23 14 32 11屏幕截图

2021 05 23 14 32 36屏幕截图

2021 05 23 14 32 50屏幕截图

2021 05 23 14 18 53屏幕截图

软件测试级别

单元测试(组件测试)

• 集成测试

• 系统测试

• 验收测试

• 维护测试

2021 05 23 14 44 20屏幕截图

从测试方法会不会关注程序内部的结构可以将其划分为:白盒测试、黑盒测试和灰盒测试,黑盒测试不关注程序内部的实现结构,仅仅是通过向程序进行输入来观察程序的输出对不对;白盒测试就需要关注程序内部的实现结构,对程序的代码逻辑结构实施相关的测试

白盒测试

结构覆盖

2021 05 24 20 39 37屏幕截图

Control Flow Graph&and Its Coverage

控制流图及其覆盖

2021 05 26 17 01 24屏幕截图

2021 05 26 17 01 42屏幕截图

2021 05 26 17 02 09屏幕截图

2021 05 26 17 02 23屏幕截图

2021 05 26 17 02 38屏幕截图

2021 05 26 17 03 36屏幕截图

语句覆盖/代码行覆盖:目标保证程序中每一条语句最少执行一次,其覆盖标准无法发现判定中逻辑运算的错误;

判定覆盖/分支覆盖:是指选择足够的测试用例,使得运行这些测试用例时,每个判定的所有可能结果至少出现一次,

                             但若程序中的判定是有几个条   件联合构成时,它未必能发现每个条件的错误;

条件覆盖:是指选择足够的测试用例,使得运行这些测试用例时,

              判定中每个条件的所有可能结果至少出现一次,但未必能覆盖全部分支;

条件组合覆盖:是使每个判定中条件结果的所有可能组合至少出现一次,因此判定本身的所有可能解说也至少出现一次,

                   同时也是每个条件的所有可能结果至少出现一次;

路径覆盖: 是每条可能执行到的路径至少执行一次,试图覆盖软件中的所有路径;

语句覆盖是一种最弱的覆盖,

判定覆盖和条件覆盖比语句覆盖强,满足判定/条件覆盖标准的测试用例一定也满足判定覆盖、条件覆盖和语句覆盖,

路径覆盖也是一种比较强的覆盖,但未必考虑判定条件结果的组合,并不能代替条件覆盖和条件组合覆盖。

条件组合覆盖是除路径覆盖外最强的

二、测试的时候:

条件组合覆盖为主, 路径覆盖为辅

三、帮助理解的小例子(借鉴他人)

  if A and B then Action1 
  if C or D then Action2 

①语句覆盖最弱,只需要让程序中的语句都执行一遍即可 。上例中只需设计测试用例使得A=true B=true C=true 即可。

②分支覆盖又称判定覆盖:使得程序中每个判断的取真分支和取假分支至少经历一次,即判断的真假均曾被满足。上例需要设计测试用例使其分别满足下列条件即可(1)A=true,B=true,C=true,D=false(2)A=true,B=false,C=false,D=false。

③条件覆盖:要使得每个判断中的每个条件的可能取值至少满足一次。上例中第一个判断应考虑到A=true,A=false,B=true,B=false第二个判断应考虑到C=true,C=false,D=true,D=false,所以上例中可以设计测试用例满足下列条件(1)A=true,B=true,C=true,D=true(2)A=false,B=false,C=false,D=false。
④路径覆盖:要求覆盖程序中所有可能的路径。所以可以设计测试用例满足下列条件(1)A=true,B=true,C=true,D=true(2)A=false,B=false,C=false,D=false(3)A=true,B=true,C=false,D=false(4)A=false,B=false,C=true,D=true。

主路径/基本路径覆盖

主路径覆盖

2021 05 24 21 52 34屏幕截图

2021 05 24 22 58 12屏幕截图

2021 05 24 21 53 10屏幕截图

2021 05 24 21 54 36屏幕截图

2021 05 24 21 56 54屏幕截图

2021 05 24 21 57 06屏幕截图

基本路径覆盖

程序的环路复杂性:程序基本路径集中的独立路径数量,这是确保程序中每个可执行语句至少执行一次所必需的测试用例数目的上界。

CC=E-V+2

独立路径:至少包含有一条在其它独立路径中从未有过的边的路径。

Data Flow Coverage

2021 05 25 23 03 18屏幕截图

2021 05 24 22 54 43屏幕截图

2021 05 25 22 53 10屏幕截图

变异测试

变异测试是一种 fault-based 的软件测试技术。这项技术已经广泛研究并使用了三十余年。它为软件测试贡献了一系列方法,工具,和可靠的结果。本文将对变异测试进行深入的调查,分析它的优势和不足之处,并对比几种不同的变异测试方法,提出一些改进的建议。

  下面用一个例子来解释什么是变异测试,考虑以下代码片段:

  if(a && b) c = 1;

  else c = 0;

条件运算符如果用||来替换&&,就会产生以下变异:

 if(a || b) c = 1;

 else c = 0;

为了杀死这个突变,需要满足以下条件:

(1)测试数据必须对突变和原始程序引起的不同状态覆盖。如:a=1,b=0可以达到目的。

(2)c的值应该传播到程序输出,并被测试检查。

弱突变覆盖需满足(1),强突变覆盖需满足(1)(2)。

271335490794607

自动化测试

黑盒测试

随机测试

随机测试(random testing,RT):首先定义程序的输入域,然后在输入域上随机选择一些点作为测试用例,比较方便实现测试的自动化。但也有一些问题:

(1)输入域的定义(define input domain):首先需要详细分析文档,选择合适的输入域

(2)随机数生成(generate random sequence):计算机系统中很难得到真正的随机数,作为替代可以采用伪随机数的生成算法,使用这些算法时需要给定合适的算法种子(random.org网站生提供了随机数生成的服务)

比较特殊的随机测试技术—模糊测试(Fuzz testing)

模糊测试可以看作是随机测试的一种特殊应用,主要应用于软件安全性测试。需要构建非法的输入攻击软件使软件崩溃。

2.

随机测试很简单,但是由于不考虑任何其他类型的信息可能会导致错误检测效率的降低。

自适应随机测试(ART): 导致程序出错的测试用例有聚集性的特点,例如矩形状分布(block),条带状分布(strip)和散点状分布(points)。前两者可以用用测试用例聚集性的特征来提高测试效率(近朱者赤近墨者黑,a passed test,nearby tests may ba passed; a failed test, nearby tests may be failed. ),提示在随机测试中选择测试用例时可以到尽可能远的地方选择,即自适应随机测试(Adaptive random test,ART)的特性。

固定候选集的自适应随机测试算法(FSCS-ART algorithm):首先随机生成第一条测试用例,如果测试终止条件(寻找到错误或者测试资源耗尽)不满足,选择下一条测试用例时首先随机生成k个候选测试用例(c1……ck),然后对于每个ci计算其与已有测试用例的距离,选择距离最大的ci,运行这条测试用例,以此类推,直到测试终止条件满足。

ART可以有效提高软件测试的效率,加快发现错误的速度,但也存在一些问题:

(1)测试用例之间距离(distance)的定义,需要分析程序的规格说明;

(2)考虑测试用例的分布情况(sampling):随机测试中测试用例在输入域中的分布是均匀的,自适应随机测试可能不满足这一需求。测试用例数量庞大时,大量测试用例聚集于输入域边界附近,为此提出了一种扩大输入域的自适应随机测试技术,首先人为扩大输入域,然后在扩大的输入域使用自适应随机测试,则大量测试用例会集中于扩大后的输入域,然后将扩大的输入域剪切,这样在剩下的区域中测试用例的分布依然均匀。

(3)前面都是连续输入域,输入域不连续呢?Anti-Random Testing:首先随机选择第一条测试用例,选择第二条测试用例使需要计算测试用例与已有的测试用例的hamming距离之和,选择距离之和最大的,以此类推,直到终止。

等价类划分

一个等价类或者等价划分是指测试相同目标或者暴露相同软件缺陷的一组测试用例

在寻找等价划分时,考虑把软件具有相似输入、相似输出、相似操作的分在一组。这些组就是等价划分

2021 05 25 19 55 27屏幕截图

边界条件/次边界条件

2021 05 25 20 00 47屏幕截图

次边界条件:2的幂,ASCII表

2021 05 25 20 07 12屏幕截图

2021 05 25 20 05 52屏幕截图

组合测试

变量之前可能有联系

暴力解决,所有变量等价类相乘

2021 05 25 20 12 54屏幕截图

复杂度太高

两两组合

2021 05 25 20 13 10屏幕截图

t-ways

2021 05 25 20 13 30屏幕截图

多变量

2021 05 25 20 15 29屏幕截图

决策表

决策表是设计黑盒测试,检查产品在输入变量各种逻辑条件的行为的宝贵工具

决策表的优点:能够将复杂的问题按照各种可能的情况全部列举出来,简明并避免遗漏。因此,利用决策表能够设计出完整的测试用例集合。
在一些数据处理问题当中,某些操作的实施依赖于多个逻辑条件的组合,即:针对不同逻辑条件的组合值,分别执行不同的操作。决策表很适合于处理这类问题。

2021 05 25 20 34 38屏幕截图

2021 05 25 20 34 58屏幕截图

2021 05 25 20 35 17屏幕截图

2021 05 25 20 35 28屏幕截图

集成测试

1. 系统集成的模式与方法

1.1 集成测试前的准备

1.2 集成测试的模式

渐增式测试模式与非渐增式测试模式 

非渐增式测试模式:先分别测试每个模块,再把所有模块按设计要求放在一起结合成所要的程序,如大棒模式。

渐增式测试模式:把下一个要测试的模块同已经测试好的模块结合起来进行测试,测试完以后再把下一个应该测试的模块结合进来测试。

1.3 自顶向下和自底向上集成方法

2021 05 26 16 30 14屏幕截图

2021 05 26 16 30 38屏幕截图

2021 05 26 16 30 56屏幕截图

1.4 大棒与三明治集成方法

2021 05 26 16 31 18屏幕截图

2021 05 26 16 31 51屏幕截图

1.5 持续集成

2. 功能测试

目的和内容:

程序安装、启动正常,有相应的提示框、错误提示等
每项功能符合实际要求
系统的界面清晰、美观
菜单、按钮操作正常、灵活,能处理一些异常操作
能接受正确的数据输入,对异常数据的输入有提示、容错处理等
数据的输出结果准确,格式清晰,可以保存和读取
功能逻辑清楚,符合使用者习惯
系统的各种状态按照业务流程而变化,并保持稳定
支持各种应用的环境
能配合多种硬件周边设备
软件升级后,能继续支持旧版本的数据
与外部应用系统的接口有效

相关的测试类型:

正确性
产品功能是否与需求和设计文档一致
可靠性
用户交互是否引发软件崩溃和其它异常
易用性
软件产品完成特定任务的难易程度

3. 回归测试

回归测试的目的
所做的修改达到了预定的目的,如错误得到了改正,新功能得到了实现,能够适应新的运行环境等;
不影响软件原有功能的正确性。

回归测试的方法
再测试全部用例
基于风险选择测试
基于操作剖面选择测试
再测试修改的部分

4. 非功能性测试

性能测试
压力测试
容量测试
安全性测试
可靠性测试
容错性测试

系统测试

验收测试

验收测试(Acceptance Test):在软件产品完成了功能测试和系统测试之后、产品发布之前所进行的软件测试活动它是技术测试的最后一个阶段,也称为交付测试。

1 什么是反射

1)java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象后,再通过class对象进行反编译,从而获取对象的各种信息

2)java属于先编译再运行的语言,程序中对象的类型在编译器就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到jvm。通过反射,可以在运行时动态的创建对象并调用其属性,不需要提前在编译期直到运行的对象是谁

2 反射的原理

Class对象的由来是将.class文件读入内存,并为之创建一个Class对象。

20201031013312441

3 反射的优缺点

1、优点:在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。

2、缺点:

(1)反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;

(2)反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

4 获取Class对象的3种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class ReflectDemo {

public static void main(String[] args) throws ClassNotFoundException {

/**
* 1. Object类的getClass方法
*/
Person p=new Person();
Class c1 = p.getClass();
System.out.println(c1);//class reflect.Person

Person p2=new Person();
Class c2 = p2.getClass();
System.out.println(c2);//class reflect.Person

/**
* 2. 数据类型的静态属性class
*/

Class<Person> c3 = Person.class;
System.out.println(c3);//class reflect.Person


System.out.println(String.class); //class java.lang.String
System.out.println(Integer.class);//class java.lang.Integer
System.out.println(int.class); //int

/**
* 3. Class类中的静态方法
*/

Class<?> c4 = Class.forName("reflect.Person");
System.out.println(c4);//class reflect.Person


}

}

5 通过Class对象获取构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class ReflectDemo2 {

public static void main(String[] args) throws Exception {
Person p=new Person();
Class<?> c = Class.forName("reflect.Person");


Constructor<?>[] constructors = c.getConstructors();
for (Constructor cc:constructors){
System.out.println(cc);//public reflect.Person() 公共构造方法
}

Constructor<?>[] constructors1 = c.getDeclaredConstructors();
for (Constructor cc2:constructors1){
System.out.println(cc2);
/**所有构造方法
* private reflect.Person(java.lang.String,int,java.lang.String)
* reflect.Person(java.lang.String,int)
* public reflect.Person()
*/
}

System.out.println("-----------");

/**
* 单个构造方法
*/
Constructor<?> constructor = c.getConstructor();//public reflect.Person()
Object instance = constructor.newInstance();
System.out.println(instance);//Person{name='null', age=0, address='null'}


// private构造方法访问失败
Constructor<?> constructor1 = c.getDeclaredConstructor(String.class, int.class, String.class);

//暴力访问
constructor1.setAccessible(true);

Object o = constructor1.newInstance("bibofu", 20, "CQU");
System.out.println(o);//Person{name='bibofu', age=20, address='CQU'}


}
}

6 通过Class对象获取成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ReflectDemo3 {
public static void main(String[] args) throws Exception {

Class<?> c = Class.forName("reflect.Person");

Field[] fields = c.getDeclaredFields();
for (Field f:fields){
System.out.println(f);
/**
* private java.lang.String reflect.Person.name
* int reflect.Person.age
* public java.lang.String reflect.Person.address
*/
}

Field name = c.getDeclaredField("name");
name.setAccessible(true);
System.out.println(name);//private java.lang.String reflect.Person.name


}
}