[SQL] 如何获取昨日每个小时的记录数量?

方法

利用左连接来实现逐小时查询(终于把数据库原理学到的知识用上了😂)

select a.click_date,ifnull(b.total,0) as total from (
SELECT date_format(date_sub(curdate(), interval 1 HOUR),'%Y-%m-%d-%H') as click_date 
union all 
SELECT date_format(date_sub(curdate(), interval 2 HOUR),'%Y-%m-%d-%H') as click_date 
union all 
SELECT date_format(date_sub(curdate(), interval 3 HOUR),'%Y-%m-%d-%H') as click_date 
union all 
SELECT date_format(date_sub(curdate(), interval 4 HOUR),'%Y-%m-%d-%H') as click_date 
union all 
SELECT date_format(date_sub(curdate(), interval 5 HOUR),'%Y-%m-%d-%H') as click_date 
union all 
SELECT date_format(date_sub(curdate(), interval 7 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 8 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 9 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 10 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 11 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 12 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 13 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 14 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 15 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 16 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 17 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 18 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 19 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 20 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 21 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 22 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 23 HOUR),'%Y-%m-%d-%H') as click_date
union all 
SELECT date_format(date_sub(curdate(), interval 24 HOUR),'%Y-%m-%d-%H') as click_date
) a 
left join (
select date_format(createTime,'%Y-%m-%d-%H') as datetime, count(*) as total from `order` where  `status` = 2 group by date_format(createTime,'%Y-%m-%d-%H')
) b 
on a.click_date = b.datetime

infull用于字段的补0,在无数据的时候可以补上0。

dateSub用于减去时间戳的时间

a主要是用来生成逐小时的时间字段

b主要是查询数据

on是用来执行left join的条件

如何在Sequel Ace中快速创建CreateTime与UpdateTime?

前言

本文章主要记录个人的开发经验,产生自最近进行的后端开发。

方法

1.切换到”结构”Tab

2.添加新的字段

3.UpdateTime字段设置为”TimeStamp”, 设置Extra为”on update CURRENT_TIMESTAMP”

4.CreateTime字段设置为”TimeStamp”, Default设置为CURRENT_TIMESTAMP

CURRENT_TIMESTAMP是MySQL自带的一个时间函数,用于生成当前的时间戳”2022-01-05T08:37:17.000Z”。

on update CURRENT_TIMESTAMP表示字段在更新时更新为当前的时间戳。

通过这样子的设定,可以将创建时间和更新时间的操作交由数据库处理,免去了开发者在后端进行额外的处理。

前端如何处理?

new Date("2022-01-05T08:37:17.000Z").getTime())

通过该方法可以快速的转换成距1970 年1 月1 日之间的毫秒数。

React脚手架V1.0

背景

最近开始捯饬React,想着学会了React就能把React Native给学习了,然后就能写App。为了能够在React Native上少踩坑,我搭建这个脚手架之后,准备开发一个博文发布系统,作为DEMO练练手。

依赖构成

  1. React核心
  2. React-router-dom 路由
  3. Redux 状态管理
  4. ESlint 代码格式管理
  5. Vite 打包工具
  6. Axios 网络请求库
  7. Antd UI库

搭建流程

1. 初始化React

1.1 模板初始化

我们可以直接使用Vite自带的模板,初始化React,在本教程中我使用的是template指令来初始化项目。

运行以下指令初始化项目

 # npm 6.0
 npm init @vitejs/app gublog(项目名称) --template react(模板名称)
 ​
 # npm 7.0+ (需要额外的双横线)
 npm init @vitejs/app gublog(项目名称) --(额外的双横线) --template react(模板名称)

运行指令后,npm将创建一个以项目名称命名的文件夹📁,来初始化项目。

初始化完成后的文件树结构为:

 root
 └── gublog(项目名称)
    ├── dist
    ├── node_modules
    ├── src
    ├── index.html
    ├── package-lock.json
    ├── package.json
    └── vite.config.js

1.2 启动环境

  1. cd 项目文件夹
  2. npm install #下载依赖
  3. npm run dev #启动开发环境
环境启动成功

出现上图的网页时,我们就已经搭建出Vite + React的开发环境

2. 安装React-router-dom

为了能够实现无刷新切换页面,此处我的脚手架也会引入官方提供的Router来做前端路由管理。

2.1 安装依赖

运行指令安装路由组件

 npm install react-router-dom --save-dev

2.2 注意⚠️

React-Router-Dom就是React Router V4的浏览器版本,原来文档的Router要被替换成BrowserRouter才能使用。为了不使用方面,我们可以使用as关键字来修改别名。

 import 'BrowserRouter' as 'Router' from 'react-router-dom'

这里我真的很想吐槽,搞一个React-Router就好了嘛🤷‍♂️,干嘛还搞一个React-Router-Dom。这东西本质上就是套娃再封装📦。而且最近的V4版本还舍弃了旧的Router标签,改成了BrowserRouter。真的是NT。

解决方法来源:

3. 安装Redux

安装Redux主要是为了使得组件的耦合降低,使得更容易的管理状态,因此被打包到脚手架当中。

3.1 安装依赖

 npm install redux --save-dev

4. 安装ESlint

4.1 安装依赖

 npm install eslint --save-dev

5. 安装Axios网络请求库

5.1 安装依赖

 npm install axios --save-dev

6. 安装AntdUI库

AntdUI是阿里针对React设计的一个UI库,开箱即用,有丰富的文档。(而且还能改颜色)

6.1 安装依赖

 npm install redux --save-dev

6.2 引入环境

  1. main.css中添加import 'antd/dist/antd.css'导入CSS库
  2. Main.jsx的代码头部添加import {组件名称} from antd导入组件

结尾

这篇文章就此告一段落。Axios和ESlint的使用我还在摸索中,后面还会继续更新,敬请期待。目前在使用这个脚手架重写GuBlog日志程序当中,后面时机成熟后也会同新版本的脚手架文章一同推出。

PPG式心率测量小程序的技术分享

故事

背景

去年6月份的时候,参加了微信小程序官方举办的《高校微信小程序开发大赛》,做出了《活力健身房》这个小程序。按照初版设计,除了可以检测用户的跑步数据外,还可以测量用户的静止心率。但由于个人除了要准备比赛,还要准备痛苦的概率论补考,所以导致日程太过于紧凑,因此在该情况下,我就只完成了第一个目标而忽略了第二个心率测量的目标没有实现。

改进

在暑假期间,处于追求完美的想法,我花了一部分的时间继续学习并完成了心率检测算法的V1.0版本。而后再在这周,时隔一年的时间,重写了检测算法,推出了V1.1的版本。(至于为什么算是1.1版,在文中会说明的😂)。

技术分享

今天我写这篇文章的主要目的是跟大家分享以下的三点内容:

  1. PPG式心率测量的原理
  2. PPG式心率测量的实现
  3. 应用到小程序端的难点

1.技术原理

光体积变化描记图法 (Photoplethysmography,简称PPG) 是借光电手段在活体组织中检测血液容积变化的一种无创检测方法。当一定波长的光束照射到指端皮肤表面,每次心跳时,血管的收缩和扩张都会影响光的透射 (例如在透射PPG中,通过指尖的光线) 或是光的反射 (例如在反射PPG中,来自手腕表面附近的光线)。当光线透过皮肤组织然后再反射到光敏传感器时,光照会有一定的衰减。像肌肉、骨骼、静脉和其他连接组织对光的吸收是基本不变的 (前提是测量部位没有大幅度的运动),但是动脉会不同,由于动脉里有血液的脉动,那么对光的吸收自然也会有所变化。[1]

PPG检测的核心是通过心脏跳动影响血管中血液光线的吸收率变化,进而影响血液对光的反射率,估算受测者的心率数据。用人话说,心脏的跳动,会影响血液反射光的强度。反映到图表上,就如下图所示,心脏的舒张和收缩,会影响光强变化,形成下图的波动曲线。而心率的估算,则是通过计算60s内,有多少个波峰或者波谷。比如60s内,图表上产生了60个个波峰,则意味着1s中,心脏收缩和扩张了一次,即跳动了一次,进而可以得出60BPM的心率数据。

PPG传感器工作原理

2.技术实现

根据技术原理可以知道,PPG检测中我们需要做的事情是,计算一段时间内出现了多少个波峰,即可估算出受测者的心率。由此我们需要考虑实现以下的几个功能:

  1. 获取视频流中的某一帧
  2. 检测传感器是否被覆盖
  3. 获取当前帧的光强数据
  4. 计算波峰个数
  5. 计算BPM

1.获取视频中的某一帧

在当今社会中,人人都会有一台的智能手机。而手机上最好的颜色传感器就是摄像头了,因此在小程序端的实现中,对于数据的来源主要是摄像头。

为了实现方便,这次我依旧是使用小程序作为我的开发平台。之所以使用小程序的核心原因是官方提供了摄像头的API,使得我可以通过简单的$API$就可以获取到来自用户摄像头的视频流数据,并且不用考虑兼容性的问题。

在小程序中,我是用的是wx.createCameraContext()API来获取到视频流,同时借助onCameraFrame()来直接获取到每一帧的视频信息,极大的降低了开发的难度。通过前述的API,我们可以直接获取到当前帧Uint8数组,也就是每个像素的RGBA值。

2.检测传感器是否被覆盖

为了避免外部光照影响血管对光线的反射,因此需要受测者的手指完全覆盖摄像头。为了检测摄像头是否覆盖,我们可以通过经验主义设定一个阈值,当每个像素的实际光强小于阈值,则代表传感器完全被覆盖,此时就可以开始测试。

3.获取当前帧的光强数据

通过API,我们可以获得视频流中特定的一帧的Uint8数据。之所以使用Uint8是因为在该RGBA中,每一个通道的颜色只有256种,而我们仅仅是需要获取该帧中的光强。因此选用Uint8可以大幅度减少运算量,提高运行效率。

通过经验主义的测量以及结合论文[2] [3]的理论支持,我们可知血液在红光和绿光的照射下的吸收率是最大的,也就意味着强度值是最大的。因此可以更好的检测到强度变化,所以在后续的开发当中,我们都将使用单个像素中的红绿通道的强度值,来作为数据的来源。由于只使用了其中两个通道的强度值,我们的运算量就可以几何级数减少。

从原来的 2^{32} 个数据转变为了 2^{16} 个数据

获取光强的方法是:

该帧的光强值 = \frac{单个通道的强度值}{像素的个数}

4.计算波峰个数

在1.0版本中我的实现方法是通过经验值测算出一条固定的基线,来判断历史采集的光强值,有多少个跃出基线的数据,就计作多少个波峰。

V1.0 固定基线法

但这种设计是有问题的,比如最右侧的波峰,因为各种外部原因强度没有达到基线,导致丢失。当通过人眼的辨识,这个波峰不属于噪声,应该是要被记录的。

另外通过测试我们可以很容易的发现下图的假波峰和假波谷的噪声信号,这种变化幅度过大的数据也应该是被丢弃的。

噪声曲线

因此在1.1版本中,基线的计算方法做了改进。通过动态基线可以适应复杂环境光线的变化,提高测量的效率和准确性。

V1.1 动态基线法

通过动态基线,判断有多少多少次实际的跃出,进而换算出有多少个波峰。

5.计算BPM

仅仅记录波峰个数是远远不够的,我们还要计算一次跃出所消耗的时间(从波谷到跃出基线到波峰所消耗的时间)。因为受测者的光线环境如果不稳定,含有频繁的光线变化,则可能导致光强变化幅度过大,造成数据的计算误差。因此我们还需要将跃出时间作为计算的一个因素,计算平均的跃出时间。最后计算出每分钟的心率次数:

BPM = \frac{测量最大时间}{平均跃出时间}

小程序端的技术难点

到目前为止,小程序端最大的难点不在于测量算法的开发上。反而是在优雅的展示上画了大量的时间。

  1. FrameData大小
  2. Canvas绘图效率低

1.FrameData大小。通过Camera组件,我们可以直接获取到手机的视频流。由于我们这个算法中仅仅需要计算帧的光强,所以仅仅需要最低质量的FrameData即可。即降低了资源的消耗也提升了算法运行的效率。通过frame-data属性,修改为small即可启用最低质量的FrameData。

2.Canvas绘图效率低。在去年小程序退出了Canvas 2D的绘图API,可以实现同层渲染。为了更高的渲染效率,我自然就是选择并使用了最新的API进行曲线的绘制。在电脑模拟器上的表现确实印证了效率更好的这一说法。但诡异的是在Android端上,Canvas2D的运行效率非常低,导致经常性假死,不知道怎么修复。因此只能暂时被搁置,待到日后有能力后再做修复。

收获

这次的开发通过学习更多前人的研究结论来帮助我将算法优化出来,收获满满。感觉到非常的振奋人心。期待后面能继续搞点好玩的DEMO。

未来的方向

最近看到Tensorflow的BlazeFace。可以用很小的体积的模型来识别人脸,感觉可以借此,结合PPG算法,实现rPPG式的检测算法,日后有时间可以继续搞一下,感觉会非常的好玩。同时Canvas的问题也需要被修复,或许能够通过WebGL来修复?但是我还不肯定,需要后续的实验才能证明,待到后面再修吧。

结尾

由于本人不是电子工程专业,所以上文中可能会涉及到部分不规范的说明和解释。如果您能够在评论中帮助我指出来并帮助我改正,我将十分感激您,谢谢。

参考

[1] ECG/PPG量测解决方案

[2] 不同波长近红外光下手掌静脉图像质量分析

[3] 血液成分对光吸收规律的实验研究

为什么图片转Base64会多出30%的数据量

背景

由于最近在准备🐧厂的面试,被问及是否知道为什么url-loader将图片转换成Base64后,会有增加33%的数据量。因此,我开始从晚上搜集这方面的资料,但遗憾的是,大多的博文的关注点基本都是在实际应用(Webpack,前端优化…)或者是Base64如何进行编码,没有很清晰的阐述为什么会多出33%的数据量的过程和原因。因此,我希望可以通过这篇博文补充这方面的讯息。

为什么会多出30%的数据

当我们把3个Byte的数据转换成为Base64的字符时,根据Base64的规则,3个Byte的字符会转换成24个二进制位。Base64一位只有6个位二进制位,因此会产生4个Byte的字符。

但是现代计算机系统当中,一个字符要有8个二进制位来存储因此,4个字符对应的6个二进制位都要补充2个0,来转换成8个二进制位的字符存储到计算机当中。因此3个Byte(24位二进制位)的数据要用4个Byte(32位二进制位)来存储。并且32位中只有24位是用来存储数据的,剩下8位都是无用的补充位。

因此我们可以很简单的计算出输出:输入 = 4:3 (33% 数据冗余). 与此同时,我们同样可以总结出,输入n Bytes,输出就有(下图)那么长。

{\textstyle 4\left\lceil {\frac {n}{3}}\right\rceil }

总结

核心思路,原来三个Byte的数据,转换成Base64后,要用四个Byte来存储。多了1/3(33%)的数据要存储,所以数据量会增多。

把照片打包进bundle.js中,看来还是要因地制宜的。url-loader默认数值应该是8192bit(1024 byte),增大33%后就是增加了300byte。因此,url-loader主要还是用于打包只有几个像素的小文件比较适合。不会对整体的加载速度有过多的影响。

引用

https://en.wikipedia.org/wiki/Base64

http://www.ruanyifeng.com/blog/2008/06/base64.html

[NodeJS]非抢占式多级反馈队列调度算法

前序

《操作系统》课程最近布置了一个大作业,要求我们每人实现一个非抢占式多级反馈队列调度算法的模拟程序,作为期末考核的一部分。(u1s1,真的是爽到,老师开心,我们也写的开心!)

实现代码

//Powered By 7gugu

//每一级队列的可运行时间
let timeSlice = [1, 2, 3];

//准备运行的队列
let preRunQueue = [];
//3级运行队列
let runQueue = [
  [],
  [],
  []
];

//准备运行的程序[开始时间, 运行时间]
let progs = [
  [0, 8],
  [1, 4],
  [5, 1],
  [3, 7],
  [4, 2]
];

for (let i = 0; i < progs.length; i++) {
  let prog = {};
  prog.id = "P" + i;
  prog.startTime = progs[i][0];
  prog.runTime = progs[i][1];
  prog.priority = 0; //设置最高优先级
  preRunQueue.push(prog); //把待运行的程序导入运行序列
}

//总运行时间
let totalTime = 0;

while (true) {
  //如果待运行队列中仍然有程序 & 程序已到达开始时间
  if (preRunQueue.length > 0) {
    let prog = preRunQueue[0];
    if (prog.startTime <= totalTime) {
      preRunQueue.shift(); //直接弹出队头的元素
      runQueue[0].push(prog); //把程序加入0级运行队列中
      console.log(prog.id + "开始运行,开始时间为:" + totalTime);
    }
  }

  for (let i = 0; i < runQueue.length; i++) {
    //如果各级队列还有程序的话,就继续运行
    if (runQueue[i].length > 0) {
      let prog = runQueue[i].shift(); //获取各级队列中第一个程序
      if (prog.runTime > timeSlice[i]) {
        //程序运行时间比时间片大
        totalTime = totalTime + timeSlice[i]; //总运行时间累加
        prog.runTime = prog.runTime - timeSlice[i]; //减去每一次运行的时间
        if (i != runQueue.length - 1) {
          //如果未处于最低优先级,则把程序放在下一个优先级队列中
          runQueue[i + 1].push(prog);
        } else {
          //如果处于最低优先级,则把程序放回最低优先级中运行
          runQueue[i].push(prog);
        }
      } else {
        //程序运行时间比时间片小
        totalTime = totalTime + prog.runTime;
        console.log(prog.id + "运行完成,目前时间为:" + totalTime);
      }
      break;
    }
  }
} 

运行结果

仓库地址

参考资料

计算机操作系统(第四版) 西安电子科技出版社

ES6中的箭头函数()=>与function的区别

  1. 写法不同
  2. this指向不同
  3. 构造函数
  4. 变量提升

1.写法不同

//function
function x(a, b){
  return a + b;
}
//箭头函数
const x = (a, b)=>{
  return a + b;
}

2.this指向不同

使用function定义的函数,this的指向随着调用环境的变化而变化的,而箭头函数中的this指向是固定不变的,一直指向的是定义函数的环境。

function x(a, b){
  console.log(this);
}
const obj = ()=>{
  test: 7gugu,
}
x(); //Window
obj.test(); //obj { test: 7gugu }

使用function定义的函数中this指向是随着调用环境的变化而变化的

//使用箭头函数定义函数 
var foo = () => { console.log(this) };
var obj = { aa:foo };
foo(); //Window
obj.aa(); //Window

明显使用箭头函数的时候,this的指向是没有发生变化的。

3.构造函数

//使用function方法定义构造函数 
function Person(name, age){     
  this.name = name;   
  this.age = age; 
} 
var lenhart =  new Person(lenhart, 25);
console.log(lenhart); //{name: 'lenhart', age: 25}
//尝试使用箭头函数 
var Person = (name, age) =>{     
  this.name = name;   
  this.age = age; 
}; 
var lenhart = new Person('lenhart', 25); //Uncaught TypeError: Person is not a constructor

function是可以定义构造函数的,而箭头函数是不行的。

4.变量提升

由于js的内存机制,function的级别最高,而用箭头函数定义函数的时候,需要var(let const定义的时候更不必说)关键词,而var所定义的变量不能得到变量提升,故箭头函数一定要定义于调用之前!

foo(); //123
function foo(){     
  console.log('123');
}  
arrowFn(); //Uncaught TypeError: arrowFn is not a function 
var arrowFn = () => {     
  console.log('456'); 
};

转载自:https://blog.csdn.net/github_38851471/article/details/79446722

Vue Production环境中Proxy无效的解决思路&方法

问题

最近上线Vue项目到服务器,上传之后就出现了,代理404的问题。Dev环境中的代理是工作正常的,这点让我很疑惑,但这恰巧是我的一个误解,下面是这次的解决思路。

解决思路

一开始,我以为vue.config.js中的,devServer中的proxy是在路由(Router)层面做的数据转发,所以在这上面花了一些时间进行研究。后续通过查阅官方文档发现,devServer配置的是一个nodejs的测试服务器参数,而不是路由参数后,恍然大悟。(这就是我的误解所在)

解决这个proxy的方向,应该是关注于配置自身的HTTP服务器的代理上面,如果是Nginx,就要配置Nginx的路由转发;我这里用的是Apache作为我的HTTP服务器,所以应该配置的是对应的代理参数。

配置步骤

HTTP服务器部分

1.打开Apache的httpd.conf,开启以下两个proxy拓展,保存

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so 

2.在https.conf中配置监听接口,保存

3.打开httpd-vhost.conf如下图配置即可

4.此时HTTP服务器部分就配置完成了,此时仅需重启Apache服务器即可生效。

Vue部分

因为是使用了History模式的路由,所以为了正确路由到相关的页面,还需要配置相关的PathRewrite才能正确路由。

1.在vue生成生产环境文件的文件夹中,添加.htaccess文件(我使用的是默认参数,所以就在dist文件夹中)

2.配置以下参数即可。(参数参考官方文档进行配置)

3.至此Vue部分配置完成,重新访问404的问题就消除了。

参考文献

  1. vue项目使用history模式打包应该注意的地方
  2. 前端用vue 上传项目,apache服务器 成功中转接口代理
  3. Apache的ProxyPass简单使用
  4. vue项目上线apache反向代理配置跨域
  5. apache proxy作用——ProxyRequests

Vue import不识别 Unexpected Token (xx:xx)

Bug出现

最近在给工作室打工的时候,Vue做了个静态路由懒加载。

然后编译的时候死活不认这个import,截图如下,完全没有解决的思路。

解决思路

  1. 同事编译莫得问题,遂排除是代码问题
  2. 清除node_module,重新npm install,无效,排除是Node的问题
  3. 因为其他位置的import工作正常,遂排除babel未启用
  4. 最后通过下载安装”syntac-dynamic-import”,在配置后,问题解决

[笔记] 设计模式: 统一建模语言

第一章

  • 课程目标
    • 统一建模语言
  • UML
    • 统一建模语言
    • 概念
      • 通用的可视化建模语言
      • 通过一些标准的图形符号和文字对系统进行建模
    • 作用
      • 对于软件进行描述、可视化处理、构建软件系统的文档
    • 视图(View)
      • 用户视图
        • 以用户的观点来标识系统的目标,它是所有视图的核心
        • 描述系统的需求
      • 结构视图
        • 表示系统的静态行为
        • 描述系统的静态元素,如包、类与对象,以及它们之间的关系
      • 行为视图
        • 表示系统的动态行为
        • 描述系统的组成元素如对象在系统运行时的交互关系
      • 实现视图
        • 表示系统中逻辑元素的分布
        • 描述系统中的文件以及它们之间的关系
      • 环境视图
        • 表示系统中物理元素的分布
        • 描述系统中的硬件设备以及它们之间的关系
      • 视图关系图
  • 图(Diagram)
    • 用例图
    • 类图
  • 模型元素(Model)
    • 模型元素
      • 就是图上面的符号
      • 用于描述事物以及事物与事物之间的关系
    • 每一个模型元素都要一个阈值相对应的图形元素
    • 同一个模型元素可以在不同的UML图中使用
    • 定义
      • 封装了数据和行为
      • 是具有相同属性、操作、关系的对象集合的总称
      • 一个类可以有多个职责(功能),但设计的好的类通常只有单一职责(功能)
    • 类的属性
      • 类的数据职责
    • 类的操作(类实现的方法)
      • 类的行为职责
  • 类图
    • 用来描述不同的类以及它们之间的关系
  • UML类图
    • 类名
  • 类的属性
  • 类的操作(其实就是类实现的方法)
  • 类之间的关系
    • 关联关系
      • 表示一类对象与另一类对象之间的关系
      • 实线连接
      • 通常将一个类的对象作为另外一个类的成员变量
      • 单向关联
  • 双向关联(就是两个类互相作为对方的成员属性)
  • 自关联(就是自己的成员属性存储了一个自身的对象)
  • 多重性关联(就是有2个+的成员变量存储/被存储了)
    • 多重表示,通常在直线上使用一个数字/数字范围来表示
  • 图例
  • 聚合关系
    • 表示整体与部分的关系(容器与成员的关系)
    • 成员对象是整体对象的一部分,但是成员对象可以脱离整体对象独立存在
    • 使用带空心菱形的直线表示
    • 图例
  • 组合关系
    • 聚合关系相同的表示
    • 成员是整体的一部分,但是成员对象跟整体对象的生命周期一样,整体对象销毁了,成员对象也会被销毁
    • 使用带实心的菱形直线表示
    • 因为是在整体(Head)对象中new的对象,销毁了head,就会连带把成员对象都销毁掉
  • 依赖关系
    • 表示一个事物使用另外一个事物
    • 体现在某个类的方法使用另一个累的对象作为参数
    • 使用带箭头的虚线
    • 依赖的一方指向被依赖的一方
  • 三种实现方法
  • 泛化关系(继承关系)
    • 用于描述父类与子类的关系
    • 用带空心三角形的直线表示
    • 图例
  • 接口与实现关系
    • 接口之间也存在继承关系和依赖关系
    • 接口与雷志坚存在一种实现的方法
    • 使用带空心三角形的虚线表示
    • 图例
  • 拓展机制
    • 注释
      • 可以给类图(UML)增加注释