虚拟 DOM 节点的好处

把渲染逻辑和真实的 DOM 进行解耦。有以下的几个好处:

  • 使得这种渲染逻辑更容易在非浏览器环境中进行重用
    • 渲染为字符串(SSR)
    • 渲染到 canvas 、 webGL 等平台
    • 渲染到原生的移动端平台(IOS 、Android)
  • 提供了一种拥有 javascript 全部表现力的方式去操作衍生结构(derivative structure, 例如 DOM) 的方式。(UI 编程(使用模板语法)约束更强,有时候用 javascript 可以更好的表达渲染逻辑)

Render function

如果我们需要用 render function 表达一个 <div> hello <span>world</span></div>, 可以安装下面的代码。

1
2
3
4
5
const App = {
render(){
return h("div", {}, ["hello", h("div", {}, "world")])
}
}

条件渲染可以直接通过三元运算符或者条件语句实现:

1
2
3
4
5
const app = {
render(){
return condition ? h("div", "foo") : h("div", "bar");
}
}

何时直接写 Render function 比模板语法方便

当我们时候 slots 且逻辑较多的时候

比方说我们需要一个会自动缩进的的元素 stack

1
2
3
4
5
6
7
8
const Stack = {
render(){
const slots = this.$slots.default ? this.$slots.default() : [];
return h("div",{class: "stack"}, slots.map(child => {
return h("div", {class: `mt-${this.props.size}`}, child)
}))
}
}

Block 的优化点

block 只会关注可能变化的点!

选择排序(selection sort)

符合直觉的简单的排序算法, 每遍历一次挑选出一个最大(最小)的数,直到最后一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function selectionSort(arr){
for(let i = 0; i < arr.length - 1; i++){
let min = i
for(let j = i + 1; j < arr.length; j++){
min = arr[j] < arr[min] : j : min
}
swap(arr, i, min);
}
function swap(arr, i, min){
let temp;
arr[i] = temp;
arr[i] = arr[min];
arr[min] = temp;
}
}

冒泡排序的时间复杂度为 O(n2) 额外空间复杂度 O(1)
是稳定的排序算法

冒泡排序(bubble sort)

比较两个相邻的元素A、B(假设 A 在 B 前面), 如果A > B(A < B)则交换 A 与 B 的顺序。
每一轮结束后,最后一个元素为最大(最小)的值。每一轮可以选出一个最大(最小值)。选出后在再其他元素中继续选。直到只剩一个元素排列完毕。
代码如下:

1
2
3
4
5
6
7
8
9
10
11
function bubbleSort(arr){
for(let i = 0; i < arr.length - 1; i ++){
for(let j = 0; j < arr.length - 1 - i; j ++){
if(arr[j] > arr[j + 1]){
const temp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = temp
}
}
}
}

冒泡排序的时间复杂度为 O(n2) 额外空间复杂度 O(1)
是稳定的排序算法

插入排序

将第一个元素视为已排序,从未排序数组中插入已排序数组。依次比较找到合适的位置后插入。
较前两个算法,这个算法的时间复杂度好了很多。不过最坏时间复杂度仍然是 O(n2), 额外空间复杂度 O(1),是稳定的排序算法
示例代码如下:

1
2
3
4
5
6
7
8
9
10
function insertionSort(arr){
for(let i = 0;i < arr.length; i++){
const item = arr.splice(i, 1)
for(let j = 0; j < i; j++){
if(arr[j] > item){
arr.splice(j,0,item)
}
}
}
}

希尔排序

希尔排序是插入排序的改进版本,本质上是将一个大数组分成几个小数组分别进行插入排序,这样来减少需要的所需的移动,间隔序列可以是一个数列。最优间隔序列未知。
下面是代码示例;

1
2
3
4
5
6
7
8
shellSort(arr){
// gap:4-->2-->1
for(let gap = Math.floor(arr.length / 2); gap > 0; i = Math.floor(gap / 2))){
for(let th = 0; th < Math.floor(arr.length / gap); th++){
insertSort(arr[gap * th, gap * (th + 1)])
}
}
}

最坏时间复杂度:O(n2)
平均时间复杂度:O(n4/3)~O(n3/2)
和间隔序列和数组本身有关

归并排序(merge sort)

终于到了常用的排序算法了。排序思路很简单。对两个排序好的数组进行合并在排序。那如果这两个排序好的数组是怎么来的呢? 细分,将数组一分为二不断细分直到粒度为 1,那么久可以开始合并了。代码如下:

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
funciton mergeSort(arr){
if (arr.length < 2) return arr;
let mid = arr.length >> 1
let left = arr.slice(0, half);
let right = arr.slice(half);

function merge(l, r){
const res = [];
let i=0, j=0;
while(1){
if(i >= l.length){
res = [...res,...r];
return res;
}
if(j >= r.length){
res = [...res,...l]
return res;
}
if(l[i] > r[j]){
res.push(r[j]);
j++;
}else{
res.push(l[i]);
i++;
}
}
}

return merge (mergeSort(left), mergeSort(right));
}

归并排序时间复杂度为 O(nlgn) 之所以有如此大的进步是因为前面的比较信息通过归并的形式保留了下来。

描述

密码截取_牛客题霸_牛客网
Catcher 是 MCA 国的情报员,他工作时发现敌国会用一些对称的密码进行通信,比如像这些 ABBA,ABA,A,123321,但是他们有时会在开始或结束时加入一些无关的字符以防止别国破解。比如进行下列变化 ABBA->12ABBA,ABA->ABAKK,123321->51233214。因为截获的串太长了,而且存在多种可能的情况(abaaab 可看作是 aba,或 baaab 的加密形式),Cathcer 的工作量实在是太大了,他只能向电脑高手求助,你能帮 Catcher 找出最长的有效密码串吗?
数据范围:字符串长度满足 1 <= n <= 2500

输入描述:

输入一个字符串(字符串的长度不超过 2500)

输出描述:

返回有效密码串的最大长度

示例 1

输入:
ABBA
输出:
4

示例 2

输入:
ABBBA
输出:
5

示例 3

输入:
12HHHHA
输出:
4

解1

遍历字符串, 判断以该字符为中心是不是回文
分为两种情况进行判断:
当该字符串的字符个数为奇数时
当该字符串的字符个数为偶数时

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
for(let i = 0; i < arr.length; i++){
const a = oddCode(i, arr);
const b = evenCode(i, arr);
const res = []
res.push(Math.max(a, b));
}

// 任意取一个字符, 判断以它为中心的奇数回文的长度
function oddCode(index, arr){
let left = index - 1;
let right = index + 1;
let count = 1;
while(left >= 0 && right < arr.length){
if(arr[left] === arr[right]){
count += 2;
left--;
right++;
}else{
return count;
}
}
return count;
}

// 任意取一个字符, 判断它是不是偶数回文串的长度
function evenCode(index, arr){
let left = index;
let right = index + 1;
let count = 0;
while(left >= 0 && right < arr.length){
if(arr[left] === arr[right]){
count += 2;
left --;
right ++;
}else{
return count;
}
}
return count;
}

递归法

首先找出问题的基本解(base case) , 再将一个问题通过分类的方法,使之化为一个个更小的问题的问题的组合。
比方说,我们找到了知道 base case f(0) = 0 然后知道 f(n) = f(n - 1) + 1 这种情况就可以通过递归来解。这有点像数学归纳法。

但是递归法解决常常会进行很多重复的计算,例如在使用递归法计算斐波那契数列的问题的时候。而且如果递归太深会有爆栈的可能。

动态规划

真是由于上面的问题,我们可以顺着来,与递归法的那种从目标结果推到基础解 (base case) 的情况不同。我们可以从 (base case) 推到目标解。为了避免重复的计算,我们可以将中间的结果保留在想数组一样的额外存储空间里。

字符串的匹配问题是较为经典的可以用动态规划解决的问题。一般步骤就是先建立一个辅助的二维数组。然后前面的数据去退出 arr[i][j]

有一点递归会比动态规划好,那就是递归每次可以根据情况每次的退化的 K,这个 K 可以是一个变量。而动态规划的每次前进 J 步,这个 J 一般都是一个定值。

双指针

双指针常见的用法是和排序结合。是一种贪心算法,是的搜索有方向。

描述

迷宫问题_牛客题霸_牛客网
定义一个二维数组 N * M ,如 5 × 5 数组下所示:

1
2
3
4
5
6
7
maze = {  
01000,
01110,
00000,
01110,
00010,
};

它表示一个迷宫,其中的 1 表示墙壁,0 表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的路线。入口点为 [0,0],既第一格是可以走的路。

数据范围: 2 <= n, m <= 10 , 输入的内容只包含 0 <= val <= 1  

输入描述:

输入两个整数,分别表示二维数组的行数,列数。再输入相应的数组,其中的1表示墙壁,0表示可以走的路。数据保证有唯一解,不考虑有多解的情况,即迷宫只有一条通道。

输出描述:

左上角到右下角的最短路径,格式如样例所示。

示例1

输入:

5 5
0 1 0 0 0
0 1 1 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0

输出:

(0,0)
(1,0)
(2,0)
(2,1)
(2,2)
(2,3)
(2,4)
(3,4)
(4,4)

示例2

输入:

5 5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 1
0 1 1 1 0
0 0 0 0 0

输出:

(0,0)
(1,0)
(2,0)
(3,0)
(4,0)
(4,1)
(4,2)
(4,3)
(4,4)

说明:
注意:不能斜着走!!

解1

思路:
使用 ‘1’ 将整个迷宫包裹起来,表示墙壁。将走过的地方也标记为 ‘1’
开始递归搜索
分为这几种情况:
碰到墙壁 return []
走到终点 return [终点]
走到走过的路径 return []

正在搜索路径的时候可能的走法
向上、向下、向左、向右
如果返回值不会 [] 则说明找到了路径,直接将当前点加入到路径返回即可。
代码如下:

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
// 获取输入
const [m, n] = readline().split(" ").map(Number);

// 建立辅助数组
const ast = new Array(m + 2);
for (let i = 0; i < m + 2; i++) {
if (i === 0) ast[i] = new Array(n + 2).fill(1);
if (i > 0 && i < m + 1) {
ast[i] = readline().split(" ").map(Number);
ast[i].push(1);
ast[i].unshift(1);
}
if (i === m + 1) ast[i] = new Array(n + 2).fill(1);
}

function findPath(ast, i, j) {
// 到达终点,直接返回
if (i === m && j === n) return [[i - 1, j -1]];

// 对于走过的点 置为 1
ast[i][j] = 1;
if (ast[i - 1][j] === 0) {
const up = findPath(ast, i - 1, j);
if(up.length) return [[i - 1, j - 1],...up]
}
if (ast[i + 1][j] === 0) {
const down = findPath(ast, i + 1, j);
if(down.length) return [[i - 1, j - 1],...down]
}
if (ast[i][j - 1] === 0) {
const left = findPath(ast, i, j - 1);
if(left.length) return [[i - 1, j - 1],...left]
}
if (ast[i][j + 1] === 0) {
const right = findPath(ast, i, j + 1);
if(right.length) return [[i - 1, j - 1],...right]
}
return [];
}

什么是 monorepo

Monorepo 是一种版本控制和软件开发方式,其中所有项目都存储在同一个代码仓库中。这意味着,所有的项目都共享相同的版本控制系统(例如 Git),并且代码、文档和资源都存储在同一个位置。

monorepo 的优点和缺点

Monorepo 的优点包括:

  • 减少了代码库管理的复杂度:因为所有项目都在同一个仓库中,所以无需管理多个代码仓库,便于统一管理。
  • 便于代码共享:因为所有项目都在同一个仓库中,所以可以轻松地在项目之间共享代码,减少代码冗余。
  • 提高了代码的一致性:因为所有项目都在同一个仓库中,所以可以方便地维护代码的一致性。

Monorepo 的缺点包括:

  • 增加了代码库的大小:因为所有项目都在同一个仓库中,所以代码库的大小会变得更大,可能会增加代码管理的复杂度。
  • 增加了代码冲突的风险:因为所有项目都在同一个仓库中,所以在多人协作开发时可能会出现冲突,需要更多的协调和沟通。

使用 pnpm 建立一个 monorepo

1
pnpm create monorepo

执行这个命令会在当前目录中创建一个名为 package.json 的文件,并将其中的 private 字段设置为 true。这样可以确保 monorepo 中的包不会被发布到 npm 上。

走方格的方案数_牛客题霸_牛客网

描述

请计算 n * m 的棋盘格子( n 为横向的格子数,m 为竖向的格子数)从棋盘左上角出发沿着边缘线从左上角走到右下角,总共有多少种走法,要求不能走回头路,即:只能往右和往下走,不能往左和往上走。
注:沿棋盘格之间的边缘线行走, 数据范围: 1 <= n, m <= 8

输入描述:

输入两个正整数n和m,用空格隔开。(1≤n,m≤8)

输出描述:

输出一行结果

示例1

输入:2 2
输出:6

解1

在这个棋盘中,你只能往右或往下走,所以你可以把它看作是在一个二维平面上走迷宫,每次只能向右或向下走一步。这样的话,你从左上角出发,要走到右下角,就必须走 $n$ 步向右和 $m$ 步向下。

因为你每次只能选择向右或向下走,所以这里的问题就转化成了在 $n+m$ 步的序列中选择 $m$ 个数作为向下走的步数,剩余的 $n$ 个数就是向右走的步数。这样的话,你就可以使用组合数的概念来计算答案。

你可以使用以下代码来计算答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function numOfWays(n: number, m: number): number {
// 由于要选择 m-1 个数,所以可以使用组合数公式计算答案
return factorial(n + m) / (factorial(m) * factorial(n));
}

// 阶乘函数
function factorial(n: number): number {
let result = 1;
for (let i = 1; i <= n; i++) {
result *= i;
}
return result;
}

// 测试
console.log(numOfWays(2, 2)); // 6

解2

我们可以建立一个二维数组 dp,其中 dp[i][j] 表示从起点走到位置 (i, j) 的方案数。
那么,我们就可以用如下的方程来求解这个问题:

1
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]

这个方程的意思是,从位置 (i, j) 可以走到的方案数就是从位置 (i - 1, j) 和位置 (i, j - 1) 走到位置 (i, j) 的方案数之和。

我们可以用以下的代码来实现这个算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function numOfWays(n: number, m: number): number {
// 初始化 dp 数组
const dp: number[][] = new Array(n + 1).fill(0).map(() => new Array(m + 1).fill(0));

// 初始化边界条件
for (let i = 0; i <= n; i++) {
dp[i][0] = 1;
}
for (let j = 1; j <= m; j++) {
dp[0][j] = 1;
}

// 循环求解 dp 数组
for (let i = 1; i <= n; i++) {
for (let j = 1; j <= m; j++) {
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}

return dp[n][m];
}

什么是单例模式

单例模式是一种常见的软件设计模式,它提供了一种方法来确保一个类只有一个实例,并且提供了一个全局访问点来访问该实例。
在单例模式中,类有以下几个角色:

  • 单例类(Singleton):它是被控制实例数量的类。
  • 客户端(Client):它是使用单例的类。

例1

我们先来看一个简单的例子:
在前端开发中,你可以使用单例模式来创建一个全局的弹窗组件。这个弹窗组件可以在任何时候被调用,并且只会创建一个实例。

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
// 定义单例类
class Popup {
// 声明私有的实例变量
private static instance: Popup;

// 私有构造函数,防止外部通过 new 关键字创建实例
private constructor() {}

// 公有的静态方法,用于获取单例的唯一实例
public static getInstance(): Popup {
// 如果实例不存在,则创建一个实例
if (!Popup.instance) {
Popup.instance = new Popup();
}
// 返回单例的唯一实例
return Popup.instance;
}

// 其他的公有方法,用于显示弹窗等功能
public show() {
console.log('显示弹窗');
}
}

// 客户端使用单例
const popup1 = Popup.getInstance();
const popup2 = Popup.getInstance();

// 两个弹窗对象都是同一个实例
console.log(popup1 === popup2); // true

// 调用弹窗的显示方法
popup1.show(); // 显示弹窗

在这个例子中,我们定义了一个 Popup 类,它有一个私有的实例变量 instance 和一个私有的构造函数,用于防止外部通过 new 关键字创建实例。此外在这个例子中,我们还定义了一个公有的方法 show,用于显示弹窗。

在客户端,我们通过调用 Popup.getInstance() 方法来获取单例的唯一实例。这样,无论你调用多少次 Popup.getInstance() 方法,都只会创建一个实例。

在这个例子中,我们通过比较两个弹窗对象是否相等,来验证单例模式是否正确实现。通过运行上面的代码,我们可以看到输出的结果为 true,说明两个弹窗对象都是同一个实例。

这就是一个使用单例模式创建全局弹窗组件的简单例子。

例2

在前端开发中,你可以使用单例模式来创建一个路由器组件。这个路由器组件可以帮助你管理页面之间的跳转,并且只会创建一个实例。

下面是使用 TypeScript 实现一个路由器组件的例子:

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
// 定义单例类
class Router {
// 声明私有的实例变量
private static instance: Router;

// 私有构造函数,防止外部通过 new 关键字创建实例
private constructor() {}

// 公有的静态方法,用于获取单例的唯一实例
public static getInstance(): Router {
// 如果实例不存在,则创建一个实例
if (!Router.instance) {
Router.instance = new Router();
}
// 返回单例的唯一实例
return Router.instance;
}

// 公有的跳转方法
public navigate(path: string) {
console.log(`跳转到 ${path} 页面`);
}
}

// 客户端使用单例
const router1 = Router.getInstance();
const router2 = Router.getInstance();

// 两个路由器对象都是同一个实例
console.log(router1 === router2); // true

// 调用路由器的跳转方法
router1.navigate('/home'); // 跳转到 /home 页面

在这个例子中,我们定义了一个 Router 类,它有一个私有的实例变量 instance 和一个私有的构造函数,用于防止外部通过 new 关键字创建实例。此外,我们还定义了一个公有的静态方法 getInstance,用于获取单例的唯一实例。在这个方法中,如果实例不存在,就会创建一个实例,然后返回单例的唯一实例。

在这个例子中,我们还定义了一个公有的方法 navigate,用于跳转到指定的页面。

在客户端,我们通过调用 Router.getInstance() 方法来获取单例的唯一实例。这样,无论你调用多少次 Router.getInstance() 方法,都只会创建一个实例。

在这个例子中,我们通过比较两个路由器对象是否相等,来验证单例模式是否正确实现。通过运行上面的代码,我们可以看到输出的结果为 true,说明两个路由器对象都是同一个实例。

单例模式的适用场景

单例模式适用的场景如下:

  • 当系统中只需要一个对象的时候,比如全局唯一的缓存对象、全局唯一的日志对象等。
  • 当系统中存在资源共享的对象时,比如数据库连接池、网络连接池等。
  • 当系统中需要限制对象数量的时候,比如许可证对象、限流器等。

单例模式可以节省系统资源,提升性能。但是,你需要注意,单例模式也会带来一些问题,比如对象的生命周期难以控制、测试困难等。所以,在使用单例模式时,你需要谨慎考虑它的优缺点,并确保它的使用符合单一职责原则。

遵循原则

单例模式遵循以下原则:

  • 单一职责原则:单例对象的职责应该单一,避免过度膨胀。
  • 开闭原则:单例对象应该对扩展开放,对修改关闭。
  • 依赖倒置原则:单例对象应该依赖于抽象,而不是具体实现。
  • 接口隔离原则:单例对象的接口应该尽量简洁,避免过多的依赖。

工厂方法模式是一种设计模式,它提供了一种方法来创建对象,而无需指定构造函数的类型。 在工厂方法模式中,使用一个专门的工厂类来创建所需的对象。 在抽象工厂类中定义了一个抽象工厂方法,该方法返回一个抽象产品类型。 具体工厂类继承抽象工厂类,并实现抽象工厂方法。 这样,在具体工厂类中就可以创建具体的产品对象。

例1

举个例子,假设我们有一个抽象产品类型为 Shape,并有两个具体产品,即 CircleSquare。 如果我们使用工厂方法模式来创建这些产品,那么我们可能会定义如下代码:

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
// 抽象产品类
abstract class Shape {
abstract draw(): void;
}

// 具体产品类 1
class Circle extends Shape {
draw() {
// 实现画圆的逻辑
}
}

// 具体产品类 2
class Square extends Shape {
draw() {
// 实现画正方形的逻辑
}
}

// 抽象工厂类
abstract class ShapeFactory {
abstract createShape(): Shape;
}

// 具体工厂类 1
class CircleFactory extends ShapeFactory {
createShape(): Shape {
return new Circle();
}
}

// 具体工厂类 2
class SquareFactory extends ShapeFactory {
createShape(): Shape {
return new Square();
}
}

工厂方法模式是一种非常有用的设计模式,它可以让我们在不指定构造函数的类型的情况下创建对象。 它可以使代码更加灵活,并且可以轻松地更改或扩展应用程序的对象创建方式。

实际上,也就是说,工厂方法不直接使用构造函数,而是使用一个工厂类,这个工厂类是和构造函数解耦。
这样,我们就可以在不指定具体的构造函数类型的情况下创建对象。 这使得代码更加灵活,并且可以轻松地更改或扩展应用程序的对象创建方式。

例子2

假设我们正在开发一个游戏,其中玩家可以使用多种武器进行战斗。 我们可以使用工厂方法模式来实现这个功能。

首先,我们可以定义一个抽象产品类型 Weapon,其中包含了武器的抽象方法 use()。 我们还可以定义几个具体产品类型,如 SwordBow,它们分别代表剑和弓。 接下来,我们可以定义一个抽象工厂类型 WeaponFactory,其中包含了一个抽象工厂方法 createWeapon(),该方法返回一个 Weapon 类型的对象。 最后,我们可以定义两个具体工厂类型,如 SwordFactoryBowFactory,它们分别继承了 WeaponFactory 类型,并实现了 createWeapon() 方法。

首先,我们定义抽象产品类型 Weapon:

1
2
3
abstract class Weapon {
abstract use(): void;
}

然后,我们定义具体产品类型 Sword 和 Bow:

1
2
3
4
5
6
7
8
9
10
11
class Sword extends Weapon {
use() {
console.log("Swinging sword!");
}
}

class Bow extends Weapon {
use() {
console.log("Shooting arrow!");
}
}

接下来,我们定义抽象工厂类型 WeaponFactory:

1
2
3
abstract class WeaponFactory {
abstract createWeapon(): Weapon;
}

最后,我们定义具体工厂类型 SwordFactory 和 BowFactory:

1
2
3
4
5
6
7
8
9
10
11
class SwordFactory extends WeaponFactory {
createWeapon() {
return new Sword();
}
}

class BowFactory extends WeaponFactory {
createWeapon() {
return new Bow();
}
}

现在,我们可以使用这些类来创建武器。例如,我们可以这样创建一把剑:

1
2
3
const swordFactory = new SwordFactory();
const sword = swordFactory.createWeapon();
sword.use(); // Output: "Swinging sword!"

我们也可以这样创建一张弓:

1
2
3
4
const bowFactory = new BowFactory();
const bow = bowFactory.createWeapon();
bow.use(); // Output: "Shooting arrow!"

工厂方法模式是一种创建型设计模式,它定义了一个创建对象的接口,但由子类决定实例化哪个类。工厂方法让类把实例化推迟到子类。

工厂方法模式的适用场景

工厂方法模式适用于以下场景:

  1. 当一个类不知道它所必须创建的对象的类的时候。
  2. 当一个类希望由它的子类来指定它所创建的对象的时候。
  3. 当类将创建对象的职责委托给多个帮助子类中的某一个,并且希望将哪一个帮助子类是代理者这一信息局部化的时候。

例如:
当你要创建一个电商网站,希望用户在结算时有多种支付方式可供选择,但是你不确定用户会选择哪种支付方式。这时候,你可以使用工厂方法模式来解决这个问题。
你可以定义一个抽象的支付方式类,然后分别定义支付宝、微信、银行卡等具体的支付方式类,最后定义一个支付方式工厂类,负责根据用户的选择创建对应的支付方式实例。这样,用户在结算时只需要选择想要使用的支付方式,工厂方法就会帮助你创建出对应的支付方式实例,而不需要关心具体的实现细节。

当你在开发一个聊天软件时,希望用户可以选择使用文本、语音、视频等不同的消息类型来聊天。这时候,你可以定义一个抽象的消息类,然后分别定义文本消息、语音消息、视频消息等具体的消息类,最后定义一个消息工厂类,负责根据用户的选择创建对应的消息实例。这样,用户在聊天时只需要选择想要发送的消息类型,工厂方法就会帮助你创建出对应的消息实例,而不需要关心具体的实现细节。

当你在开发一个图像处理软件时,希望用户可以选择使用不同的图像处理算法来处理图像。这时候,你可以定义一个抽象的图像处理类,然后分别定义黑白化、灰度化、锐化等具体的图像处理类,最后定义一个图像处理工厂类,负责根据用户的选择创建对应的图像处理实例。这样,用户在处理图像时只需要选择想要使用的图像处理算法,工厂方法就会帮助你创建出对应的图像处理实例,而不需要关心具体的实现细节。

0%