编码与开发
编码技术与开发杂谈
操作
备忘录
启动博客预览:
bundle exec jekyll serve
vscode 中 ctrl + shift + p,python:select interpreter 即可导入虚拟环境
vscode 中 alt + shift + 鼠标拖动可以创建多个光标同时编辑,也可以 ctrl + 上下键创建多个光标
pip freeze
可以将环境下的包以 requirements.txt 的格式导出,使用pip install -r requirements.txt
即可得到一个一样的环境服务器上同时存在 gcc/g++-11 和 gcc/g++-12,也同时存在多版本的 CUDA,通过改 bashrc 或者临时 export 环境变量来切换。
更新 windows 系统环境变量之后要重启才能生效
代码快捷键失效多半是快捷键冲突了
开了大写锁定之后标点也会变成英文的半角
fn+\键切换背光设置
在装配体中新建零件,保存到外部文件,单独打开零件,利用装配体中的参考画完之后,单独打开外部零件断开外部参考(断开之前显示——➡️,断开之后显示—x),然后删除相关的草图约束,参考平面可能也需要重新选一个平行的。
替换 keil 安装目录下的 global.prop 文件可以美化界面
带参数的宏:使用
do{...}while(0)
构造后的宏定义不会受到大括号、分号等的影响,总是会按你期望的方式调用运行。CLion 远程开发 CUDA 程序,在 CMakeLists 添加
1 2
set(CMAKE_CUDA_ARCHITECTURES 89) set(CMAKE_CUDA_COMPILER /usr/local/cuda-11.8/bin/nvcc)
conda 常用命令
1
2
3
4
5
conda create -n my_env_name python=3.10
conda activate my_env_name
conda deactivate
conda env list
conda install -c anaconda pkg_name
linux bash 常用命令
1
2
3
4
5
6
source ~/.bashrc # 刷新环境变量
lsb_release -a # 查看系统版本
uname -m # 查看系统架构
cp source_file destination_file # 在当前目录下复制文件
cp source_file /path/to/destination/ # 复制文件到另一个目录
cp -r source_directory /path/to/destination/ # 复制文件夹
Linux 命令
命令格式
Unix/Linux 命令的基本格式为command [options] [arguments]
- 命令名称
command
:这是执行的指令,例如ls
、cp
、mv
等 - 选项或参数
[options]
:这些是用来修改命令行为的标志,通常以短选项(单个破折号和一个字母,例如l
)或长选项(两个破折号和一个单词,例如-all
)的形式出现;短选项一般可以组合使用 - 操作对象
[arguments]
:这是命令要操作的文件、目录或其他对象
这个格式其实是文档编写和命令行工具帮助文档中的一种常见约定:
- command:表示命令名称,必选项,执行的命令。
- [options]:可选项,表示命令的选项参数。
-
:必选项,表示命令的操作对象或参数。 - [argument]:可选项,表示可选的操作对象或参数。
{choice1 choice2 choice3}:必选项,表示在给定选项中选择一个。 - -long-option:表示长选项,通常用于详细说明命令的功能。
- option: 表示短选项,通常是长选项的首字母或缩写。
- …:表示可以重复的参数或选项,表示多个相同类型的参数。
通配符
Linux 命令行支持一系列的通配符,常见的通配符如下
星号(*):匹配零个或多个字符。例如
*.txt
会匹配所有以.txt
结尾的文件。问号(?):匹配任意单个字符。例如
?.txt
会匹配所有单个字符后跟.txt
的文件,如a.txt
,但不会匹配ab.txt
。方括号([ ]):
- 匹配方括号中的任意单个字符。例如,
file[1-3].txt
会匹配file1.txt
、file2.txt
和file3.txt
。 - 可以包含字符范围,如
[a-z]
匹配任意小写字母,[0-9]
匹配任意数字。 - 通过使用
!
或^
在方括号内部的第一个位置,可以表示不匹配这个集合中的字符。例如,file[!0-9].txt
匹配不含数字开头的文件名后跟.txt
。
- 匹配方括号中的任意单个字符。例如,
花括号({ }):
- 匹配花括号内的字符串中的任意一项。例如,
file{1,2,3}.txt
会匹配file1.txt
、file2.txt
和file3.txt
。 - 也可以用来创建序列,如
{a..z}
或{1..10}
。
- 匹配花括号内的字符串中的任意一项。例如,
反斜线(\\):用于转义特殊字符,使之成为字面量。例如,
\\?[]
需要在命令行中用\\
来转义,如\\*
表示字面量星号而不是通配符。
复杂命令
grep
:grep(Global Regular Expression Print)是用于搜索文件中匹配给定模式的行的工具,支持正则表达式,允许复杂的搜索模式,功能强大。基本用法是grep 'pattern' filename
在文件中搜索匹配pattern
的行,并将其打印出来。例如,grep -i 'error' logfile.txt
会搜索logfile.txt
中所有包含“error”(不区分大小写)的行。- 常用选项:
i
:忽略大小写。v
:反向匹配,即显示不匹配的行。c
:统计匹配行的数量。n
:显示匹配行的行号。r
:递归地搜索目录中的所有文件。
- 常用选项:
wc
:(Word Count)用于计算文本的行数、词数或字节数。基本用法是wc [options] [file]
。例如,wc -l filename.txt
将输出filename.txt
中的行数。- 常用选项:
l
:仅计算行数。w
:仅计算词数。m
:仅计算字数。c
:仅计算字节数。
- 常用选项:
管道符
|
:用于将一个命令的输出作为另一个命令的输入,这使得用户可以将多个简单的命令链接起来,执行复杂的任务。管道符的基本语法为:command1 | command2
,command1
的输出会直接传递给command2
作为输入。使用技巧
组合多个命令:可以使用管道符将多个命令串联起来,比如你可以使用
grep
来过滤ls
的输出,然后用sort
来排序结果。ls -l | grep ".txt" | sort
高级过滤:利用
grep
命令进行正则表达式匹配,筛选出符合特定模式的行。dmesg | grep -i error
文本处理:利用
awk
和sed
这样的文本处理工具,可以对数据进行更复杂的处理。例如,提取文本中的特定列并排序。cat data.txt | awk '{print $2}' | sort | uniq
使用
tee
命令同时输出到文件和屏幕:tee
是三通管道,tee
命令读取标准输入,将内容写入文件,并同时输出到标准输出。ls -l | tee output.txt | grep "config"
Linux GNU 工具链版本选择
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
which cc # 查看当前系统中cc命令的路径及相关信息, which命令用于查找并显示给定命令的绝对路径
/usr/bin/cc
ls -l /usr/bin/cc # 查看/usr/bin/cc文件的详细信息
lrwxrwxrwx 1 root root 20 10月 1 2024 /usr/bin/cc -> /etc/alternatives/cc
ls -l /usr/bin/gcc # 查看/usr/bin/gcc文件的详细信息
lrwxrwxrwx 1 root root 6 8月 5 2021 /usr/bin/gcc -> gcc-11
ls -l /etc/alternatives/cc # /etc/alternatives/存储了可替代程序的符号链接, cc是指向实际编译器gcc的符号链接
lrwxrwxrwx 1 root root 12 10月 1 2024 /etc/alternatives/cc -> /usr/bin/gcc
update-alternatives --display gcc # 查看linux下gcc命令的多版本管理情况,输出类似下面这样
gcc - auto mode # gcc-12 的优先级是100,gcc-11 优先级是10,优先级高的被自动选中
link best version is /usr/bin/gcc-12
link currently points to /usr/bin/gcc-12
link gcc is /usr/bin/gcc
/usr/bin/gcc-11 - priority 10
/usr/bin/gcc-12 - priority 100
# 在bashrc中设置环境变量来切换使用的gcc版本
export PATH=~/my_gcc_version/bin:$PATH
# 验证
which gcc
gcc --version
/usr/bin/gcc
命令是一个软链接,由 update-alternatives
管理,自动指向优先级最高的版本。有 sudo 权限时可以更改 priority 来改变默认使用的 gcc 版本。没有 sudo 权限时,可以在 bashrc 中 export 环境变量来指定要使用的 gcc 版本。
CLion 远程开发设置 CMake
执行以下步骤来指定 CMake 指定的版本
- 设置环境变量 C=/usr/bin/gcc-11;CXX=/usr/bin/g++-11
- 设置工具链:C 编译器和 C++编译器分别指定为/usr/bin/gcc-11、/usr/bin/g++-11
- CMake 中的 CMake 选项填写-DCMAKE_C_COMPILER=/usr/bin/gcc-11 -DCMAKE_CXX_COMPILER=/usr/bin/g++-11
注:理论上这样设置就可以了,但目前尚不清楚是由于什么原因,导致设置之后并不能稳定成功。(当时 CLion 的远程开发功能尚在 beta 阶段,不知道是不是软件本身的 bug,没有进一步死磕,暂时就记录到这里)
捣鼓这个的起因是 A100 的 gcc 默认为 gcc-12,但是 cuda-11.8 不支持 gcc-11 之后的 GNU 编译链;正常来说,如果 gcc 版本兼容,CLion 不需要进行任何设置。
几种 pip 安装
python setup.py install
是最传统的安装方式,直接调用包内的 setup.py
脚本,通过 setuptools
或 distutils
库执行安装逻辑,会将包文件复制到 Python 环境的 site-packages
目录,卸载需要需手动删除文件
pip install <包名>
是现代 Python 推荐的安装方式,通过 pip
工具从 PyPI 仓库 下载并安装公开的第三方包,自动处理依赖关系
pip install .
是安装 本地包 的方式(.
表示当前目录),本质是用 pip
处理当前目录下的 setup.py
或 pyproject.toml
,用 pip
的逻辑处理本地包,依赖处理更可靠
虚拟环境和 Anaconda
安装的 python,主要包含了 Lib 库(包含标准库和 site-packages 第三方包存储位置)、Scripts(包含 pip.exe 包管理器)以及 python.exe 解释器;安装的时候还安装了 python 启动器(用于管理多个不同版本的 python)。虚拟环境即将上面的东西复制一份(一般情况下不含标准库,标准库通过符号链接或直接引用系统 python 安装路径下的标准库;但 anaconda 出于环境隔离的考虑,会将标准库一并复制;以及会将解释器和包管理器统一放在 Scripts 路径下,主要是为了方便添加环境变量)。
虚拟环境本质上是对解释器、包管理器的一种隔离式复制;虚拟环境的激活和去激活就是在环境变量 Path 中添加或去除虚拟环境的解释器路径,相当于对安装位置默认的路径进行截胡。在虚拟环境下安装第三方库,就会安装到虚拟环境的路径下了。
base
是 Anaconda 安装后默认创建的虚拟环境,为了让用户开箱即用,默认在终端启动时就激活 base 环境。如果不激活任何虚拟环境,就在操作系统本地的环境(也叫 “系统 Python” 或 “裸机环境”)中,容易造成 依赖污染、库版本冲突、难以迁移项目 Denham 问题,这正是虚拟环境存在的原因。Anaconda Prompt 本质上是一个 CMD 命令行 + 初始化脚本。它启动时根据使用的 shell 调用不同的脚本,然后设置好 PATH
等环境变量,并激活 (base)
环境。
GitHub 无法拉取代码
表现为 GitHub 长期处于半墙状态,挂梯子才能流畅访问,即使开启梯子,clone 仍速度慢/失败。这是因为 git 默认情况下不使用系统代理。
系统代理: 系统代理是通过设置网络协议中的代理服务器,使网络请求(如 HTTP、HTTPS、SOCKS 协议)先通过代理服务器再转发到目标服务器。用户设备上的应用程序会根据系统代理设置(有些应用会选择不转发,即系统代理是非强制的),将流量发送到指定的代理服务器进行处理。系统代理通常只处理特定类型的网络流量,如 HTTP 或 HTTPS 流量。
TUN 模式: TUN 模式则是在操作系统中创建一个虚拟的网络接口,该接口能够接收和发送 IP 数据包。通过 TUN 模式,所有的网络流量(不仅限于某种协议,而是所有 IP 层的流量)都可以被转发到虚拟网络接口,并通过一个隧道(通常是加密的 VPN 隧道)传输到远程服务器。这使得 TUN 模式可以捕获和转发任何网络协议的流量,包括 TCP、UDP、ICMP 等。
如果使用 clash、v2ray 等工具配置,可以开启 TUN 模式,简单快捷,一劳永逸(wsl 和 VSCode 等软件默认都不会使用系统代理)。
如果梯子被封装为程序,一般通过系统代理实现。此时需要修改 git 配置。此时需要打开梯子,在设置-网络和 Internet-代理-使用代理服务器中查看代理 IP 地址和端口,然后打开 powershell,配置 git
1
2
git config --global http.proxy <http://127.0.0.1:7890> # 换成在设置中看到的IP和端口
git config --global https.proxy <https://127.0.0.1:7890> # 换成在设置中看到的IP和端口
原理
挂载
在 Linux 系统里,挂载就是把一个存储设备(硬盘分区、U 盘等)接入到文件系统的某个目录下,这样你才能通过这个目录访问设备里的文件。插上 U 盘只是让系统识别到这个设备(/dev/sdX),但是文件系统还没有连接到你想访问的目录上。挂载就是把它“接到树上”,让你能用文件路径访问它。
1
2
3
4
# 把设备 /dev/sda1(通常是一个磁盘分区)挂载到 ./u120 这个目录下
mkdir u120 && mount /dev/sda1 ./u120
# 显示系统中所有挂载点的磁盘使用情况
df -h
NFS 是网络文件系统,有服务器端和客户端两部分。服务器即提供文件的设备,通常是 PC;通过编辑/etc/exports
文件决定要共享的目录。客户端即使用这些文件的设备,通常是板子;用 mount 命令,把服务器上的目录挂载到自己设备的某个目录下。
1
2
# /home/hrx/ws是主机上要被共享的路径,192.168.1.123是主机的ip; 通过将主机的这个目录挂载到板子的/mnt目录,就可以在板子上通过访问/mnt目录来访问到主机上的/home/hrx/ws目录
mount -t nfs 192.168.1.123:/home/hrx/petalinux_ws/nfs_ws /mnt
内存模型
内存模型是针对编程语言的概念,描述了编程语言如何抽象和访问内存;存储器硬件只提供线性的地址空间,不同编程语言需要在这个线性空间上建立自己的软件抽象以便于程序员使用。C 抽象为字节序列+指针、Java 抽象为对象+堆、Python 抽象为对象+垃圾回收堆。内存模型的设计初衷是屏蔽硬件差异,保证程序在不同平台之间的可移植性。
C/C++的内存模型如下,在编译生成的 map 文件中可以找到对应的概念。
命令行工具
命令: 即 Linux 程序。一个命令的本质就是一个 Linux 的可执行程序。命令一般没有图形化界面,但是可以在命令行中通过字符化的反馈与我们交互。
命令行:即 Linux 终端(Terminal),是一种命令提示符页面。以纯字符的形式操作系统,可以使用各种字符画命令对系统发出操作指令。终端是人机交互的窗口,是一个图形化或文本界面程序。
命令行工具 CLI:CLI 是 Command-Line Interface 的简称,泛指通过输入命令和计算机交互的模式。
Shell 是一个接收、解释并执行命令的程序。CMD、PowerShell、Bash 都是 Shell 的一种。CMD(命令提示符)是 Windows 的传统命令行程序,运行 .bat
文件、支持基础命令;PowerShell 是更强大的 CLI 工具,支持对象管道、脚本系统,是 CMD 的“升级版”;Bash 是 GNU Shell,Linux/WSL/Mac 默认终端;zsh 是 Z Shell,是 bash 的超集,有高亮、补全等功能。
ARM 开发涉及的编译器
- ARM Compiler 5 (AC5) :这是 Keil 提供的早期编译器版本,基于 ARM 公司的 RealView Compiler 技术。AC5 是一个成熟的编译器,支持多种 ARM 架构,但功能相对有限。ARM Compiler 6 (AC6) :这是 Keil 提供的最新编译器版本,基于 LLVM/Clang 技术。AC6 提供了更好的性能、更高的代码优化能力和对现代 ARM 架构(如 Cortex-M 系列)的支持。AC6 是 Keil 的推荐编译器。
- ARM Compiler :这是 Keil 提供的官方编译器,分为 AC5 和 AC6 两个版本。AC6 基于 LLVM/Clang 技术,因此也被称为
ARMCLANG
。 - GCC Compiler :GNU Compiler Collection(GCC)是一个开源的编译器集合,支持多种编程语言(C、C++、汇编等),并广泛用于嵌入式开发。Keil 支持使用 GCC 编译器进行项目开发。
- ARM 的 Clang 编译器是基于 LLVM(Low Level Virtual Machine)项目的 Clang 编译器实现。Clang 是一个现代的编译器前端,支持 C、C++ 和 Objective-C 等语言,并且具有模块化的设计,易于扩展和维护。
- C 语言编译器 :ARM 的 Clang 编译器主要用于编译 C 语言代码。它支持标准的 C 语法,并且可以生成高效的机器码。
- 汇编语言编译器 :ARM 提供了专门的汇编器(如
armasm
或armclang
的汇编模式),用于编译汇编语言代码。汇编代码通常用于性能敏感的部分或底层硬件操作。 - GCC(GNU Compiler Collection)是由 GNU 项目开发的一套开源编译器集合,支持多种编程语言(包括 C、C++、Fortran、Ada 等)。GCC 是嵌入式开发中非常流行的编译器,尤其在裸机开发和 Linux 系统开发中被广泛使用。
- ARMCLANG 是 Keil 提供的商业编译器,专为 Keil MDK 设计,而 GCC 是开源编译器,适用于多种开发环境。
arm-none-eabi-
是 GCC 工具链中的一个目标前缀,表示编译器的目标平台是基于 ARM 架构的嵌入式系统,没有操作系统(None),并且使用 Embedded ABI(Application Binary Interface)。eabi 表示使用 Embedded Application Binary Interface,这是一种专门为嵌入式系统设计的二进制接口规范。
Pytorch 使用
pytorch 是基于 python 的开源机器学习框架,机器学习的基本流程包括:数据集准备和加载、网络模型构建、前向传播、计算损失、反向传播、使用优化器进行梯度下降、保存训练模型。pytorch 对其中的每个步骤都提供了丰富的 API,在使用框架构建机器学习系统时,每个步骤主要的关注点如下
- 数据集准备和加载:数据集的组织方式以及提供 img 和 label 的访问接口、使用 transforms 进行预处理(ToTensor、Resize、Normalize、Compose 等)、加载方式(batch_szie、shuffle、num_workers)。另外,pytorch 提供了图像分类、目标检测、语义分割等任务的常见公开数据集可供直接使用。
- 网络模型构建:继承 nn.Module 类并使用 nn 中提供的各种层并配置相应的参数来构建网络,常用的层包括 Conv、MaxPool、LInear、ReLU、Sigmoid、Flatten 等等,可以使用 Sequential 将它们串联起来。另外,pytorch 图像分类、目标检测、语义分割等任务的经典网络模型可供直接使用。
- 前向传播:将输入传入构建的模型并得到预测输出并记录计算图。计算图即用于记录从输入一步步算到输出的过程的数据结构。
- 计算损失:使用 nn.loss 并指定损失函数,计算输出和目标的损失,常见的损失函数包括 L1、MSE、交叉熵等。
- 反向传播:调用 loss 的 backward 方法,通过链式法则计算损失函数对模型参数的梯度。pytorch 中通过 autograd 机制实现反向传播,当参数 tensor 的 requires_grad 属性为 True 时,在调用 backward 方式并传入模型参数时,就会自动追踪并计算梯度。
- 梯度下降:使用 optimizer 并选用优化器算法,根据反向传播计算出的梯度更新模型参数,常见的优化器算法包括 SGD、Adam 等。
- 模型保存:推荐用 save 方法保存模型的 state_dict 只保存模型参数,可以减小模型大小;加载时使用 load_state_dict 即可。
pytorch 默认会对所有 tensor 记录计算图以便反向传播求梯度,但并不是所有操作都需要梯度(全部追踪会浪费内存和计算),只有用于训练模型的前向传播、损失计算、反向传播以及其它需要对模型参数求导的地方需要追踪梯度,其它如模型推理、日志保存、指标计算等都不需要追踪梯度。通过 with torch.no_grad():这个上下文管理器,with 代码块中的语句会临时关闭梯度追踪。
pytorch 默认会对梯度进行累加,通常来说需要在每轮执行完 step 的参数更新到下一轮 backward 之间,调用 optimizer 的 zero_grad 方法清除之前的梯度以确保只记录当轮梯度。只有一些特殊情况下(如显存不足需要累积多个小批次的梯度后再更新参数),才会每 N 轮后清零一次梯度。
在 torch.cuda.is_available() = True 的情况下,推荐使用 gpu 进行训练,具体操作上即将模型和输入数据(参与计算图运算的数据)迁移到 gpu 上,使用.cuda()方法可迁移到默认 gpu 上,使用.to(device)方法可以更灵活地迁移到目标设备(支持 CPU 和多 GPU)。另外,可使用 torch.cuda.empty_cache()手动释放未使用的张量缓存。
Numpy 使用
numpy 以具有维度的数组来组织数据,最常用的是 python 语法中的切片操作,如 array[1::2, 3]表示第二行开始到最后一行每隔一行,第四列;另外注意 numpy 的广播机制,很方便但也容易埋坑。shape 方法查看形状,reshape 方法重塑形状,如三维数组(2, 4,3)可以理解为两个四行三列的二维数组叠起来,dtype 属性查看数据类型或定义时设置数据类型;zeros、ones、full、eys、random、randn、radint 方法填入形状参数可以填充数组元素,concatenate 方法可以合并两个数组。
Git 工作流
git 管理文件有三种状态:已修改,已暂存,已提交
.gitignore 配置不提交的文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
## git工作流
git clone <url> ## 克隆仓库
git checkout -b <branch_name> ## 创建个人分支
## 写代码
git add <filename> ## 文件修改放入暂存区
git commit -m "commit 内容" ## 提交 commit
git push origin <branch_name> ## 个人分支提交到远程仓库
git checkout master ## 切换到主分支
git pull origin master ## 拉取主分支的更新
git checkout <branch_name> ## 切换到个人分支
git rebase master ## 将主分支的更新同步到个人分支 (如果有冲突需要手动解决)
## 创建 pull request
容器技术
通过操作系统级虚拟化,将应用及其依赖打包成容器,实现 “一次构建,到处运行”;但在实现上不虚拟整个操作系统,只虚拟应用运行所需的库、依赖、配置文件等必要组件,并与宿主机共享操作系统内核,比虚拟机更轻量化。比较出名的容器技术有 docker、k8s。
docker 简单来说就是用容器化技术给应用程序封装独立的运行环境,每个运行环境就是一个容器,运行容器的计算机称为宿主机。镜像是容器的模板。每个 docker 都运行在独立的虚拟环境中,容器的网络与宿主机是隔离的。
设计模式
设计模式是面向对象系统的代码设计思想,核心思想是提供可复用的解决方案,让代码更易维护、扩展和理解。设计模式独立于具体编程语言,不同编程语言特性差异很大,有的语言本身就是为了贯彻某个设计原则而被开发出来的,有的语言提供了针对某些设计模式的语法糖,同一种设计模式在不同语言中也可能有不同的实现方式。
代码的设计其实自底向上分为三层,最基本的是低耦合高内聚的设计思想,往上是面向对象的设计原则,再往上才是各种设计模式。设计模式依赖接口这个抽象规范,接口是抽象方法的集合,在底层实现接口时,必须按照接口给定的调用方式来实现,而对高层模块则隐藏了类的内部实现。
面向对象的设计原则
面向对象的三大法则是封装继承多态,三者是递进关系。封装就是有意识地把对象的属性和方法分为对内的和对外的;继承是为了提高代码的复用性,子类可以直接复用父类的属性和方法,并可以实现新功能或覆写父类方法的实现;多态指同一操作作用于不同对象时,会产生不同的执行结果。
面向对象的设计原则包括:
- 开放封闭原则:对扩展开放,对修改关闭,即支持加新功能,但最好不要修改已经成型的代码。
- 里氏替换原则:引用父类的对象要能透明地使用其子类的对象,即子类和父类的方法,实现上可以不同,但输入和返回需要一致。
- 依赖倒置原则:高层模块不应该依赖底层模块,两者应该依赖接口这个抽象。抽象不应该依赖细节,细节应该依赖抽象,即要针对接口而不是针对实现编程。
- 接口隔离原则:客户端不应该依赖那些它不需要的接口,因此要使用多个专门的接口而不是单一的总接口。
- 单一职责原则:一个类只负责一项职责,不要存在多个导致类变更的原因。
设计模式分类
设计模式大致分为创建者模式、结构型模式、行为型模式。创建者模式解决如何创建对象的问题;结构型解决对象之间怎么组织的问题;行为型模式解决如何实现方法的问题。
- 创建者模式:简单工厂模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式、单例模式
- 结构性模式:适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式、代理模式
- 行为型模式:解释器模式、责任链模式、命令模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、访问者模式、模板方法模式
创建者模式
简单工厂模式:通过工厂类负责创建产品类的实例,隐藏创建对象的实现,但违反了单一职责原则和开闭原则。
工厂方法模式:将工厂分为抽象工厂角色和具体工厂角色,一个工厂角色只负责创建一个产品类的实例。
抽象工厂模式:每一个具体工厂都生产一套产品而不是一个,有利于维护产品之间的约束关系,但难以支持新种类的抽象产品。
建造者模式:用指挥者角色以同样的构建过程创建不同的表示,有利于约束构建顺序。
原型模式:对于需要被复制的类,从内部提供克隆的接口方法,使得在某些属性和方法对外部不可见的情况下,外部也能对类进行复制。
单例模式:保证一个类只有一个实例并提供一个全局的访问点,相当于全局变量的同时防止了命名空间被污染。单例模式分饿汉式和懒汉式,饿汉式即在类加载时进行实例化,懒汉式即在第一次使用时进行实例化。
一般来说,以简单工厂或工厂方法开始,当发现设计需要更大的灵活性时,再向抽象工厂或建造者演化。
结构性模式
适配器模式:使得原本由于接口不兼容而不能一起工作的类可以一起工作,实现方式有多继承(类适配器)和组合(对象适配器)两种,多继承即同时继承多个类,组合即将要适配的类作为适配器的属性,以在适配器中调用需要的属性和方法。
桥接模式:将一个实物的两个维度分离成抽象和实现,将继承关系转换为组合关系,两个维度都可以独立扩展,提高了扩展的灵活性。
组合模式:将对象组合成树形结构,使得用户对单个对象和组合对象的使用具有一致性,角色包括抽象组件、叶子组件、复合组件。
装饰模式:在不改变原有对象结构的前提下,动态地给对象添加额外功能。它通过创建一个包装类(装饰器)来包裹原始对象,允许在调用原始对象方法的前后添加新的行为,同时保持接口的一致性。
外观模式:定义高层接口来统一调用子系统中的功能,使得子系统组合起来的功能更易于使用,实质是多一层封装。
享元模式:通过共享技术减少系统中对象的数量,节省内存空间并提高性能;适用于存在大量相似或相同对象的场景,通过复用已存在的对象来避免重复创建,仅在必要时才创建新对象。
代理模式:为真实对象提供一种代理来以控制这个对象的访问,角色包括抽象实体(作为接口)、实体(真实对象)、代理(和真实对象使用一致的代理对象);常见的应用场景如远程代理、虚代理、保护代理。
行为型模式
解释器模式:一般用于做编译器,解释器是负责解释文法规则的模块,解释器模式是一种语法解释器的开发框架。
责任链模式:使多个对象连成一条链并沿着这条链传递请求,链上的对象都有机会处理请求,避免在客户端处理发送者和接收者之间的耦合关系,一个对象无需知道是其它哪一个对象处理其请求,要求链上的对象有同样的处理接口并存储链的下一级对象。
命令模式:一般用于做桌面程序、命令行工具等,将请求转换为一个包含与请求相关的所有信息的独立对象。将对象的具体实现(行为的实现者)通过接口与业务逻辑(行为的请求者)分离。
迭代器模式:一般用于实现迭代器这种数据结构,将遍历逻辑与聚合对象分离,使得同一聚合对象可以支持多种遍历方式,同时遍历方式的变化不会影响聚合对象本身。
中介者模式:减少对象之间混乱无序的依赖关系,限制对象之间的直接交互,迫使它们通过中介者对象进行合作。
备忘录模式:允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态,用于实现对象的 “快照” 功能和撤销操作。核心结构包括原发器(可以创建备忘录或从备忘录恢复)、备忘录(存储原发器的内部状态,对除了原发器之外的外部隐藏细节)、负责人(管理备忘录的存储和获取,但不直接操作备忘录中的状态)。
观察者模式:又称发布订阅模式,当一个对象的状态改变时,所有依赖它的对象都得到通知并被自动更新,角色包括抽象发布者、具体发布者、抽象观察者、具体观察者。
状态模式:将对象在不同状态下的行为封装到独立的状态类中,使得对象的状态变化时,其行为能自动切换到对应状态的实现,从而避免使用大量的条件判断。内部状态需抽象,行为与状态匹配,不同状态关联不同行为导致不同表现效果。状态模式是状态机思想的一种实现方式。
策略模式:封装一系列可相互替换的算法,算法独立于使用它的客户而变化,即提供相同行为的不同实现,角色包括抽象策略、具体策略和上下文,但客户必须了解不同的策略。
访问者模式:在不修改已有类的前提下,为一组不同类型的对象添加新的操作。它通过将操作逻辑与对象结构分离,使得操作可以独立于对象而变化。定义一个访问者对象,来访问一组不同类型的元素,并对每个元素执行特定操作,而无需在元素类中直接实现这些操作。
模板方法模式:定义一个操作中的算法骨架,而将一些步骤延迟到子类中,可以在不改变算法结构的情况下可重定义操作的某些步骤。角色包括抽象的原子操作/钩子操作(实现模板方法作为算法的骨架)和具体类(实现原子操作)。
博客搭建
本站基于 Jekyll 主题 Chirpy 进行构建,使用 Github Pages 服务完成部署,前置知识为 HTML + CSS + JS 基础语法、Liquid 模板语法、Jekyll 框架使用。开发环境需要安装 Ruby 和 Jekyll。完成后根据 Chirpy 官方提供的使用说明进行配置,就可以得到一个能用的静态博客站点。
在构建过程中,为了自定义外观并添加一些外围的小功能,对源码稍有改动,记录踩过的坑如下:
最开始使用官方推荐的 chirpy-starter 模板chirpy-starter。这个仓库将站点的大部分代码隐藏在 gems 包中,通过在 Gemfile 中使用
gem "jekyll-theme-chirpy", "~> 7.2", ">= 7.2.4"
来引入,适合于想专注于博客内容而不希望花太多精力来定制外观和功能的情况。(使用命令bundle info --path jekyll-theme-chirpy
可以定位本地 gem 包中的对应文件)
如跟本站一样有自定义外观和添加功能的需求,需要使用完整版的仓库jekyll-theme-chirpy官方的使用说明和 CSDN 那篇文章有一点没有说清楚,在部署阶段选择源为“GitHub Actions”之后,需要到 Actions 选项卡开启 Actions,否则提交修改不会触发部署工作流。
将本地修改推送到远程仓库以触发 Github 的 Actions 时,有一项 commitlint 的工作流,即检查 commit 信息是否符合 type(subject): body 的规范(注意英文冒号后有一个空格)。如果 commit 信息不符合规范,会出现报错,因此要注意 commit 信息格式。
开发过程中由于需要对源码作修改但是最开始又使用了 chirpy-starter 模板,后来索性将完整版的仓库直接拉到本地 chirpy-starter 的目录下进行合并,合并之后貌似将 commitlint 的工作流给覆盖掉了,当时没有打算细究 Actions,所以没有将其恢复回来。
在开发过程中,由于尝试使用 Font Awesome 库之外的图标,需要将 svg 图标转为 font 引入,在这个文件末尾添加了自定义样式,而 Vscode 的 Prettier 插件在执行代码格式化时
将:
1
@use 'main.bundle';
格式化为:
1
@use 'main';
由于production
两端各多了一个空格,导致 scss 文件编译出了错误的 css 文件,本地测试没有问题而推送到远程,页面样式渲染出现问题。这个问题在完整版仓库的 Discussion 中也有提及: