适配器模式 + 装饰器模式 + 享元模式

[Musings]

  1. 今天回看工厂模式和抽象工厂模式,发现了其实本质上所有button/textbox抽象类和实现类都是通用的,唯独的不同在于工厂的性质变了,一个注重垂类产品,一个注重品牌哈哈哈

  2. 开始结构型的设计模式了

Adapter Pattern

题目是不同国家的充电头电压适配

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

//USBTarget.interface
public interface USBTarget {
void charge5V();
}

//AmericanCharger
public class AmericanCharger {
public void supply110V(){
System.out.println("American Charger, output 110V");
}
}

//ChineseCharger
public class ChineseCharger{
public void supply220V(){
System.out.println("Chinese Charger, output 220V");
}
}

//AmericanAdapter(核心)
public class AmericanAdapter implements USBTarget {
private AmericanCharger americanCharger;

public AmericanAdapter(AmericanCharger americanCharger) {
this.americanCharger = americanCharger;
}

@Override
public void charge5V() {
americanCharger.supply110V();
System.out.println("American Adapter, output 5V!");
}
}

//ChineseAdapter(核心)
public class ChineseAdapter implements USBTarget {
private ChineseCharger chineseCharger;

public ChineseAdapter(ChineseCharger chineseCharger) {
this.chineseCharger = chineseCharger;
}

@Override
public void charge5V() {
chineseCharger.supply220V();
System.out.println("Chinese Adapter, output 5V!");
}
}

//AdapterType
enum AdapterType {
CHINESE, AMERICAN
}

//AdapterFactory
public class AdapterFactory {
private AdapterFactory() {
}//私有化实例

public static USBTarget createChineseAdapter() {
return new ChineseAdapter(new ChineseCharger());
}

public static USBTarget createAmericanAdapter() {
return new AmericanAdapter(new AmericanCharger());
}
}

//Phone.class
public class Phone {
public static USBTarget withAdapter(AdapterType adapterType) {
return switch (adapterType) {
case CHINESE -> AdapterFactory.createChineseAdapter();
case AMERICAN -> AdapterFactory.createAmericanAdapter();
default -> throw new IllegalArgumentException();
};
}

public void charging(USBTarget usbTarget) {
System.out.println("Start Charging...");
usbTarget.charge5V();
}
}

//Main.class
public class Main {
public static void main(String[] args) {
Phone phone = new Phone();
phone.charging(Phone.withAdapter(AdapterType.CHINESE));
phone.charging(Phone.withAdapter(AdapterType.AMERICAN));
}
}

加了点工厂模式进去,为了方便实例化,本质的核心是Adapter,通过多封装了一层适配器,去解决,其实就是解耦,phone不需要去考虑对应的charger

装饰器模式

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
//Weapon.interface
public interface Weapon {
int getAttack();
String getDescription();
}

//Arrow.class
public class Arrow implements Weapon{

@Override
public int getAttack() {
return 30;
}

@Override
public String getDescription() {
return "木弓";
}
}

//Sword.class
public class Sword implements Weapon {
@Override
public int getAttack() {
return 50;
}

@Override
public String getDescription() {
return "钢铁长剑";
}
}

//WeaponEnchantment.class
public abstract class WeaponEnchantment implements Weapon {
Weapon enchantmentWeapon;

public WeaponEnchantment(Weapon weapon) {
this.enchantmentWeapon = weapon;
}
}


//FireEnchantment.class 这是核心类,主要就是这里去读取旧攻击力和新的攻击力,就等于是附魔了
//这就是装饰器模式,可以给不同的武器附魔
public class FireEnchantment extends WeaponEnchantment {
private String enchantmentAttribute = "🔥";
private int enchantmentAttack = 5;

public FireEnchantment(Weapon weapon) {
super(weapon);
}

@Override
public int getAttack() {
int originAttack = this.enchantmentWeapon.getAttack();
int enchantment = originAttack + this.enchantmentAttack;
return enchantment;
}

@Override
public String getDescription() {
String originDesc = this.enchantmentWeapon.getDescription();
return originDesc + "[" + this.enchantmentAttribute + "附魔]";
}

}

//Main.class
public class Main {
public static void main(String[] args) {
FireEnchantment fireEnchantmentArrow = new FireEnchantment(new Arrow());
System.out.println(fireEnchantmentArrow.getDescription());
System.out.println(fireEnchantmentArrow.getAttack());

Weapon fireSword = new FireEnchantment(new Sword());
System.out.println(fireSword.getDescription()); // 钢铁长剑附🔥
System.out.println(fireSword.getAttack()); // 55
}
}

装饰器模式就是一个附魔的过程,需要去记忆的就是 抽象类去实现weapon接口,然后附魔类去给武器附不同属性的魔

🏭 工厂方法:一个产品,多种实现
🏭 抽象工厂:一套产品,整体替换
🔨 建造者:复杂对象,分步构建
📋 原型:已有对象,复制使用
👑 单例:全局唯一,严格控制
🎁 装饰器:相同接口,功能叠加(像俄罗斯套娃)
🔌 适配器:接口转换,兼容协作(像电源转接头)

享元模式

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
//Particle.class
public interface Particle {
void render(int x, int y, int life);
}

//Particle.type
public enum ParticleType {
RED_SPARK,
BLUE_SPARK
}

//SparkParticle.class
public class SparkParticle implements Particle {
String color;
String texture;

public SparkParticle(String color, String texture) {
this.color = color;
this.texture = texture;
}

@Override
public void render(int x, int y, int life) {
System.out.println("渲染" + color + texture + "在位置(" + x + "," + y + "), 生命周期:" + life);
}
}

//ParticleFactory.class
public class ParticleFactory {
private final Map<ParticleType, Particle> pool = new HashMap<>();

public Particle getParticle(ParticleType type) {
// 如果存在则返回共享对象,否则创建并缓存
return pool.computeIfAbsent(type, this::createParticle);
}

public Particle createParticle(ParticleType type) {
return switch (type) {
case RED_SPARK -> new SparkParticle("red", "spark");
case BLUE_SPARK -> new SparkParticle("blue", "spark");
default -> throw new IllegalArgumentException();
};
}
}

//Main.class
public class Main {
public static void main(String[] args) {
ParticleFactory particleFactory = new ParticleFactory();
Particle particle = particleFactory.getParticle(ParticleType.RED_SPARK);
particle.render(1,2,3);
Particle particle2 = particleFactory.getParticle(ParticleType.RED_SPARK);
particle2.render(1,2,3);
}
}

享元模式的核心: 分清内部元素和外部元素,控制动与静,动静分离是核心

🏭 工厂方法:一个产品,多种实现
🏭 抽象工厂:一套产品,整体替换
🔨 建造者:复杂对象,分步构建
📋 原型:已有对象,复制使用
👑 单例:全局唯一,严格控制
🎁 装饰器:相同接口,功能叠加(像俄罗斯套娃)
🔌 适配器:接口转换,兼容协作(像电源转接头)
🏷️ 享元模式:共享内在,传递外在(像活字印刷)

拉闸!

建造者模式 & 原型模式

[Musings]
最近沉迷设计模式了,挺好玩的,今天是建造者模式,最常见的builder,通过链式调用来方便构造,这个是我日常使用中觉得最实用的

建造者模式

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
//Order.class
public class Order {
private String mainFood;
private int number;
private String address;

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

public void setMainFood(String mainFood) {
this.mainFood = mainFood;
}

public void setAddress(String address) {
this.address = address;
}

public void setNumber(int number) {
this.number = number;
}
}
//OrderBuilder.interface
public interface OrderBuilder {
OrderBuilder setMainFood(String mainFood);

OrderBuilder setNumber(int number);

Order build();
}
//MeituanOrderBuilder.class
public class MeituanOrderBuilder implements OrderBuilder {
private Order order;

public MeituanOrderBuilder(String address) {
this.order = new Order(address);
}

@Override
public OrderBuilder setMainFood(String mainFood) {
this.order.setMainFood(mainFood);
return this;
}

@Override
public OrderBuilder setNumber(int number) {
this.order.setNumber(number);
return this;
}

@Override
public Order build() {
return this.order;
}
}
//Main.class
public class Main {
public static void main(String[] args) {
MeituanOrderBuilder builder = new MeituanOrderBuilder("123");
Order abc = builder.setMainFood("abc")
.setNumber(12)
.build();
}
}

这是第一个简单版本的,然后我就想,如果在调用的时候可以写成下面这个版本,就更舒服了

1
2
3
4
5
6
7
8
9
public class Main {
public static void main(String[] args) {
// MeituanOrderBuilder builder = new MeituanOrderBuilder("123");
Order abc = Order.withMeituanBuilder()
.setMainFood("abc")
.setNumber(12)
.build();
}
}

然后我就开始班门弄斧,其实就是抽象工厂+建造者模式,builder返回不同的抽象工厂,然后每个工厂去做建造

1
2
3
4
5
6
7
8
package org.example.builder;

public class Order {
// {...}
public static MeituanOrderBuilder withMeiTuanBuilder(String address){
return new MeituanOrderBuilder(address);
}
}

主要这里考虑的几个点:

  1. 建造者通过对超多参数的情况进行优化,防止构造函数过于复杂
  2. 通过链式调用来清晰明了的书写代码
  3. 考虑是否使用final来修饰字段,这样可以确保建造者模式在set完以后是固定的

第三点其实根据场景和设计需求考虑,设计模式本身就是灵活的,一起都要基于业务场景的上下文来实现,而不是单纯的照抄,理解核心原理,就像打太极一样,学会了,又好像没学,什么都忘了,想用的时候,让灵感飘动起来

所以到此为止,记住特性的区别
🏭 工厂方法:一个产品,多种实现
🏭 抽象工厂:一套产品,整体替换
🔨 建造者:复杂对象,分步构建
📋 原型:已有对象,复制使用
👑 单例:全局唯一,严格控制

原型模式

原型模式的核心就是两个

  1. 实现cloneable接口,重写clone方法
  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
45
46
//Address.java
public class Address implements Cloneable{
private String city;
private List<String> tags = new ArrayList<>();

public List<String> getTags() {
return tags;
}

public void setTags(List<String> tags) {
this.tags = tags;
}

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}

//拷贝构造函数
public Address(Address other){
this.city = other.city;
this.tags = new ArrayList<>(other.tags);
}

//手动实现拷贝构造函数
@Override
protected Object clone() throws CloneNotSupportedException {
Address clone = (Address) super.clone();
clone.tags = new ArrayList<>(this.tags);
return clone;
}
}

//Main.class
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address();
Address clone = (Address) address.clone();
address.getTags().add("1");
System.out.println(address); //tag :[1]
System.out.println(clone); // tag: []
}
}

其实很简单,就这么个逻辑

一篇搞明白JS变量提升

[Musings]
一篇文章搞清楚 JS 变量提升

最基础的变量提升

1
2
3
4
5
var a = 100;
console.log(a); // 100
console.log(b); // undefined
var b = 100;
console.log(b); // 100

真正的运行的时候编译器其实偷偷做了这一步:

1
2
3
4
5
6
var a = 100;
var b; //我提升了!升华了!✅ 声明提升到作用域顶部
console.log(a); // 100
console.log(b); // undefined
b = 100; //✅ 赋值留在原地
console.log(b); // 100

行,这是第一步,变量提升,接着看看函数提升

1
2
3
4
5
6
var a = 100;
test(); //test
console.log(a); // 100
function test() {
console.log("test");
}

看完变量和方法的变量提升,我们来看看 let 和 const

1
2
3
4
5
let a = 100;
console.log(a); // 100
console.log(b); // ReferenceError
let b = 100;
console.log(b); // 上一步就挂了,走不到这一步的

接着我们继续看看方法体作用于中的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function test() {
for (var i = 0; i < 3; i++) {
console.log(i); // 0,1,2
}
console.log(i); //3
})();
console.log(i); //ReferenceError

(function test() {
for (let i = 0; i < 3; i++) {
console.log(i); // 0,1,2
}
console.log(i); //ReferenceError
})();

基本到这差不多就能理解变量提升了,但是有一种比较特殊的异步+变量提升的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(function test() {
// var i 被提升到这里
for (var i = 0; i < 3; i++) {
// 3个setTimeout回调都闭包引用同一个 i
setTimeout(() => {
console.log(i); //3,3,3
}, 100);
}
// 循环结束时 i = 3
// 100ms后所有回调执行,都读取同一个 i(值为3)
})();

(function test() {
for (let i = 0; i < 3; i++) {
// 每次迭代创建新的词法环境,有独立的 i
setTimeout(() => {
console.log(i); //0,1,2
}, 100);
}
})();

解释下这里发生这个情况的原因:

简洁的表达就是
“var 让所有回调共享同一个变量引用,最终都看到循环结束值 3。let 每次迭代创建新的词法环境,每个回调捕获独立的变量值。”

我脑子里的啰嗦想法就是 ⬇️

var 的作用域是函数作用域而 let 的作用与是块级作用域,所以这里所有的异步任务会被丢到异步队列,然后在执行 log 的时候,i 已经变成 3 了,所以输出了三遍
而 let 是块级作用域,所以在执行的时候每一个 log 函数都带一个变量 i,每个变量 i 在单独的内存空间里,所以不会互相污染,这就是 var 导致的变量提升所带来影响,从本身的额块级作用域被提升到了函数作用域,所以被 log 函数共享了内存空间

以上我的讲解都是自己的理解,实际上对于 let i 的变量在内存中都是独立的这点,得根据 javascript 引擎的写法来看,可能引擎会重用寄存器中的地址所以从
描述规范上来说,更准确的讲法是,let i 在词法环境里是互相隔离的

继续变量提升的case:

1
2
3
4
5
6
7
8
9
console.log(typeof func); // "function" ✅

var func = "I'm a variable";

function func() {
return "I'm a function";
}

console.log(typeof func); // "string" ✅

显而易见,函数提升的优先级高于变量

最后感受下块级作用域的隔离

1
2
3
4
5
6
7
8
9
10
11
12
{
function innerFunc() {
var secret = "我是秘密"; // 🔒 函数作用域
return secret;
}

console.log(innerFunc()); // "我是秘密" ✅
// console.log(secret); // ❌ ReferenceError ✅
}

console.log(innerFunc()); // ❌ 可能报错(取决于环境,严格模式下会报错)

最后附上一个对比图吧

特性 var let/const 函数声明
提升 ⚠️暂时性死区 ⚠️暂时性死区
作用域 函数级 块级 块级
重复说明
异步闭包 共享应用 独立 独立

Singleton & Factory

[Musings]
今天开始了设计模式的滚动上手学习,来增强下整体的设计思路

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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package org.example.factory.playground;
/*
题目

场景描述:
假设你正在开发一个跨平台的UI组件库,需要支持Windows和macOS两种操作系统。UI组件包括按钮(Button)和文本框(TextBox)。

要求:

使用工厂模式创建单个UI组件
使用抽象工厂模式创建整套UI组件
实现以下类:

抽象产品:Button, TextBox
具体产品:WindowsButton, WindowsTextBox, MacButton, MacTextBox
工厂类:ButtonFactory, TextBoxFactory(工厂模式)
抽象工厂:UIFactory(抽象工厂模式)
具体工厂:WindowsUIFactory, MacUIFactory(抽象工厂模式)
请实现上述所有类,并编写一个客户端代码演示两种模式的使用。

*/

public class FactoryMain {
public static void main(String[] args) {
System.out.println("=== 工厂模式演示 ===");
// 工厂模式
ButtonFactory buttonFactory = new ButtonFactory();
TextBoxFactory textBoxFactory = new TextBoxFactory();
Button button = buttonFactory.createButton("windows");
TextBox textBox = textBoxFactory.createTextBox("windows");
button.click();
button.print(); // 补充print方法调用
textBox.input(); // 补充input方法调用
textBox.print();

System.out.println("\n=== 抽象工厂模式演示 ===");
// 抽象工厂模式
UIFactory macUIFactory = new MacUIFactory();
UIFactory windowsFactory = new WindowsUIFactory();

Button macButton = macUIFactory.createButton();
TextBox macTextBox = macUIFactory.createTextBox();
macButton.click();
macButton.print();
macTextBox.input();
macTextBox.print();

Button windowsButton = windowsFactory.createButton();
TextBox windowsTextBox = windowsFactory.createTextBox();
windowsButton.click();
windowsButton.print();
windowsTextBox.input();
windowsTextBox.print();
}
}

package org.example.factory.playground;

abstract class Button {
public void click() {
System.out.println("button clicked");
}

abstract void print();
}

package org.example.factory.playground;

class ButtonFactory {
public Button createButton(String type) {
switch (type) {
case "windows":
return new WindowsButton();
case "mac":
return new MacButton();
default:
return new WindowsButton();
}
}
}

package org.example.factory.playground;

class MacButton extends Button {
@Override
void print() {
System.out.println("Mac Button print!");
}

@Override
public void click() {
System.out.println("Mac button click!");
}
}

package org.example.factory.playground;

class MacTextBox extends TextBox {
@Override
void print() {
System.out.println("Mac TextBox input!");
}
}

package org.example.factory.playground;

public class MacUIFactory extends UIFactory{
@Override
Button createButton() {
return new MacButton();
}

@Override
TextBox createTextBox() {
return new MacTextBox();
}
}


package org.example.factory.playground;

abstract class TextBox {
public void input() {
System.out.println("input activate");
}

abstract void print();
}

package org.example.factory.playground;

class TextBoxFactory {
public TextBox createTextBox(String type) {
switch (type) {
case "windows":
return new WindowsTextBox();
case "mac":
return new MacTextBox();
default:
return new WindowsTextBox();
}
}
}

package org.example.factory.playground;

public abstract class UIFactory {
abstract Button createButton();
abstract TextBox createTextBox();
}


package org.example.factory.playground;

class WindowsButton extends Button {
@Override
void print() {
System.out.println("Windows Button print!");

}

@Override
public void click() {
System.out.println("Windows button click!");
}
}

package org.example.factory.playground;

class WindowsTextBox extends TextBox {
@Override
void print() {
System.out.println("WindowsTextBox input!");
}
}

package org.example.factory.playground;

public class WindowsUIFactory extends UIFactory {
@Override
Button createButton() {
return new WindowsButton();
}

@Override
TextBox createTextBox() {
return new WindowsTextBox();
}
}


这里包含了抽象工厂模式和工厂模式,主要区别可以这么记忆

  1. 工厂模式: 聚焦于产品,打个比方就是
    手机专卖店: iphone手机,huawei手机,xiaomi手机
    音箱专卖店: bose音箱, apple音箱,小爱音箱

  2. 抽象工厂: 聚焦于产品簇, 打个比方就是
    原木风家居: 原木风桌子,原木风椅子,原木风床

所以:

工厂模式适合: 产品种类相对独立,不需要强一致的风格
客户端需要灵活组合不同来源的产品

抽象工厂模式适合: 需要确保一系列产品的兼容性和一致性
产品间有强烈的主题/风格关联
约束用户只使用同一系列的产品

工厂模式的实际应用:*
// BeanFactory - 最经典的工厂模式
ApplicationContext context = new ClassPathXmlApplicationContext(“beans.xml”);
UserService userService = context.getBean(“userService”, UserService.class);

// Log4j2 / SLF4J
Logger logger = LoggerFactory.getLogger(MyClass.class);
// 根据配置返回Log4jLogger、JULLogger等具体实现

// JDBC DriverManager
Connection conn = DriverManager.getConnection(url, user, password);
// 根据URL返回MySQL、PostgreSQL、Oracle等具体连接

抽象工厂模式的实际应用
// JavaFX / Swing 的主题系统
// 你实现的例子就是最典型的应用!
UIFactory factory = Platform.getUIFactory();
Button btn = factory.createButton();
Menu menu = factory.createMenu();

// 不同数据库的DAO工厂
DAOFactory factory = DAOFactory.getFactory(DBType.MYSQL);
UserDAO userDAO = factory.getUserDAO();
OrderDAO orderDAO = factory.getOrderDAO();
// 保证所有DAO使用同一种数据库

// 不同消息中间件的工厂
MessageFactory factory = MessageFactory.getKafkaFactory();
Producer producer = factory.createProducer();
Consumer consumer = factory.createConsumer();
AdminClient admin = factory.createAdminClient();

// AWS / Azure / Google Cloud 的抽象工厂
CloudFactory factory = CloudFactory.getAWSFactory();
StorageService storage = factory.createStorageService();
ComputeService compute = factory.createComputeService();
DatabaseService db = factory.createDatabaseService();

第二个讨论下 Singleton

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
class EagerSingleton {
// 类加载时就创建实例
private static instance: EagerSingleton = new EagerSingleton();

// 私有构造器
private constructor() {
console.log("EagerSingleton 实例被创建");
}

// 公共访问方法
public static getInstance(): EagerSingleton {
return EagerSingleton.instance;
}

public showMessage(): void {
console.log("Hello from EagerSingleton!");
}
}

// 使用
const eager1 = EagerSingleton.getInstance();
const eager2 = EagerSingleton.getInstance();
console.log(eager1 === eager2); // true,同一个实例

class LazySingleton {
private static instance: LazySingleton | null = null;

private constructor() {
console.log("LazySingleton 实例被创建");
}

// 简单的懒加载
public static getInstance(): LazySingleton {
if (LazySingleton.instance === null) {
LazySingleton.instance = new LazySingleton();
}
return LazySingleton.instance;
}

public showMessage(): void {
console.log("Hello from LazySingleton!");
}
}

// 使用
const lazy1 = LazySingleton.getInstance(); // 第一次调用时创建
const lazy2 = LazySingleton.getInstance();
console.log(lazy1 === lazy2); // true

唯一的区别就是懒汉和饱汉,懒汉就是在调用的时候才生成,容易造成多线程问题,饱汉就是直接初始化要用的时候直接返回,天生的线程安全,但资源浪费

前K个高频元素

[Musings]
开始设计模式的日子了,先从K个高频的算法题开始,慢慢来

原体https://leetcode.cn/problems/g5c51o/description/

先本能写第一版,按照最普通的思路

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
function topKFrequent(nums: number[], k: number): number[] {
let sortMap = new Map();
//最简单的版本就是先跑出来,便利数组,然后计算出现次数
//时间复杂度O(n),做一遍traversal
nums.forEach(val => {
let target = sortMap.get(val);
if (target) {
sortMap.set(val, ++target);
} else {
sortMap.set(val, 1);
}
})
let result = [];
//遍历k次获取最高频元素,因为map没法排序
//时间复杂度O(k),做k次遍历,所以时间复杂度为O(n*k)
for (let i = 0; i < k; i++) {
let valS = -Infinity;
let keyS = -Infinity;
sortMap.forEach((val, key) => {
if (val > valS) {
valS = val;
keyS = key
}
})
sortMap.delete(keyS);
result.push(keyS);
}
return result;
};

以上的时间复杂度是O(n k),现在开始优化第一版,主要的额思路就是使用array的内部排序这样复杂度降低到O(n logn)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function topKFrequent(nums: number[], k: number): number[] {
let sortMap = new Map<number, number>();
for (const val of nums) {
sortMap.set(val, (sortMap.get(val) || 0) + 1);
}
//这里的内置排序用的是O(logn)复杂度的,所以这里的复杂度是O(n*logn)
//这里更多的注意点就是熟练使用内部的操作符来简化代码,提高可读性
const sorted = Array.from(sortMap.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, k)
.map(entry => entry[0]);
return sorted;
};

最后考虑堆化,排序么,自然而然想到堆化

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
function topKFrequent3(nums: number[], k: number): number[] {
let map = new Heap();
let sortMap = new Map<number, number>();
for (const val of nums) {
sortMap.set(val, (sortMap.get(val) || 0) + 1);
}
for (const pair of sortMap.entries()) {
map.push(pair)
}
let res: number[] = [];
for (let i = 0; i < k; i++) {
res.push(map.pop()[0]);
}
return res;
}

//ts默认没有堆,所以日常手撕一个,顺便适应下这个题目,所以每个元素都是数组
class Heap {
private maxHeap: [number, number][];
constructor() {
this.maxHeap = [];
}
push(val: [number, number]) {
this.maxHeap.push(val);
let curInd = this.size() - 1;
while (true) {
let parentInd = this.parent(curInd);
if (parentInd < 0) {
break;
}
let parentVal = this.maxHeap[parentInd];
if (parentVal[1] < val[1]) {
this.swap(parentInd, curInd);
curInd = parentInd;
} else {
break;
}
}
}
swap(i: number, j: number) {
let temp = this.maxHeap[i];
this.maxHeap[i] = this.maxHeap[j];
this.maxHeap[j] = temp;
}
peek() {
return this.maxHeap[0];
}
pop() {
const res = this.maxHeap[0];
this.swap(0, this.size() - 1);
this.maxHeap.pop();
if (this.size() == 0) return res;
let currentInd = 0;
while (true) {
let val = this.maxHeap[currentInd][1];
let left = this.maxHeap[this.left(currentInd)];
let right = this.maxHeap[this.right(currentInd)];
let maxInd;
if (left == undefined) break;
if (right == undefined) maxInd = this.left(currentInd);
else {
maxInd = right[1] > left[1] ? this.right(currentInd) : this.left(currentInd);
}
if (maxInd < 0 || this.maxHeap[maxInd][1] < val) break;
this.swap(currentInd, maxInd);
currentInd = maxInd;
}
return res;
}
size() {
return this.maxHeap.length;
}
isEmpty() {
return this.size() == 0;
}
left(i: number) {
let ind = 2 * i + 1;
return ind >= this.size() ? -1 : ind;
}
right(i: number) {
let ind = 2 * i + 2;
return ind >= this.size() ? -1 : ind;
}
parent(i: number) {
return Math.floor((i - 1) / 2);
}
}

今天就到这吧,拉闸!

Boyer-Moore

[Musings]
忙着给artchais赶进度,最近算法题都疏忽了,今天小刷一下,进入状态

题目如下:
https://leetcode.cn/problems/majority-element/
169. 多数元素
给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:
输入:nums = [3,2,3]
输出:3
示例 2:
输入:nums = [2,2,1,1,1,2,2]
输出:2

提示:
n == nums.length
1 <= n <= 5 * 104
-109 <= nums[i] <= 109

进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。

自己手写的第一版就是用map去解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function majorityElement(nums: number[]): number {
let time = new Map<number, number>();
for (let i = 0; i < nums.length; i++) {
const val = nums[i];
let mapV = time.get(val);
if (mapV != null) {
mapV++;
time.set(val, mapV);
} else {
mapV = 1;
time.set(val, mapV);
}
if (mapV >= Math.ceil(nums.length / 2)) {
return val;
}
}
return -1;
};
majorityElement([2,2,1,1,1,2,2]);

正常思维去考虑的空间换时间就是这么玩,用hashmap去计数,但是这题的背景条件就是求多数元素,那就可以换个思路,用抵消的想法,因为多数元素的个数大于数组长度的一半,所以就直接使用抵消的思想就行了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//1.符合本题要求的复杂度Boyer-Moore投票算法思路
function majorityElement(nums: number[]): number {
let candidate = nums[0];
let count = 0;
for (let i = 0; i < nums.length; i++) {
if (count == 0) {//发现新候选人跟之前的不一样,就改一下候选人
candidate = nums[i];
}
if (candidate == nums[i]) {//当前候选人票+1
count++;
} else {//不是当前候选人,票-1
count--;
}
}
return candidate;
};

每次找到相同的值,count++,不然就count–,然后当count=0的时候,再发现值不同就重置candidate

二分查找+搜索插入位置

[Musings]
今天写的是二分查找+搜索插入位置,二分我以为5分钟能写出来,结果竟然完全忘记用维护指针去做,而是一个劲在那用步长去维护,直接导致边界震荡,最后看了眼思路,才想起来,二分是用指针的…哎.还是得不停的刷题吗….

一开始写的版本
https://leetcode.cn/problems/binary-search/description/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function search(nums: number[], target: number): number {
if (nums.length == 0) return -1;
let lastInd = 0;
let mid = Math.floor(nums.length / 2);
while (true) {
if (nums[mid] == target) return mid;
if (mid <= lastInd) return -1;
let step = mid - lastInd;
lastInd = mid;
if (nums[mid] > target) {
mid = Math.floor(mid - Math.abs(step / 2));
} else {
mid = Math.floor(mid + Math.abs(step / 2) + 1);
}
if (mid < 0 || mid >= nums.length) return -1;
}
}

偷看解题思路以后用双指针去维护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function search(nums: number[], target: number): number {
let left = 0;
let right = nums.length - 1;
while (left <= right) {
let mid = Math.floor((left + right) / 2);
if (nums[mid] == target) return mid;
if (nums[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}


// search([-1, 0, 3, 5, 9, 12], 2)
// search([5], -5)
search([0, 3, 5], 1)

写了个快乐数https://leetcode.cn/problems/happy-number/description/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function isHappy(n: number): boolean {
let set = new Set();
while (true) {
let arrayList = n.toString().split('');
let result = 0;
arrayList.forEach(s => {
result += Math.pow(Number.parseFloat(s), 2);
})
if (result == 1) return true;
if (set.has(result)) return false;
set.add(result);
n = result;
}
};
console.log(isHappy(19))

优化下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function isHappy(n: number): boolean {
const seen = new Set<number>();

const getNext = (num: number): number => {
let sum = 0;
while (num > 0) {
//1.不做字符转换,直接通过%效率更高
//2.不用forEach,效率不高,在TS中forEach属于使用callback的高阶函数,在回调中会产生自己的this,闭包绑定
const digit = num % 10;
sum += digit * digit;
num = Math.floor(num / 10);
}
return sum;
};

while (n !== 1 && !seen.has(n)) {
seen.add(n);
n = getNext(n);
}

return n === 1;
}

console.log(isHappy(19)); // true

使用快慢指针的思路来判断是否产生循环,从而减少空间复杂度,时间换空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function isHappy(n: number): boolean {
let fast = n;
let slow = n;
while (true) {
fast = getNext(getNext(fast));
slow = getNext(slow);
if (fast == 1) return true;
if (fast == slow) return false;
}

function getNext(val: number): number {
let result = 0;
while (val != 0) {
let x = val % 10;
result += x * x;
val = Math.floor(val / 10);
}
return result;
}
};
console.log(isHappy(2))

LCR 078. 合并 K 个升序链表的感想

[musings]
今天写了下 https://leetcode.cn/problems/vvXgSW/description/ ,其实不是特别难,暴力写法也还行但是排序的时间复杂度用堆可以从O(N)压缩到O(log N),但是在用堆写的时候就碰到点问题,js没有内置堆,就手写一个,手写的过程中有几个小问题引发的深思,记录下

Q1:pop和peak的区别?
A: Peak就是游戏里的peak,看一眼最大值,不拿走….

Q2:为什么大顶堆(举例)在pop的时候官方写的版本需要把头尾呼唤然后pop,而不是直接用shift?
A: 如果头尾不互换,直接使用shift,则数组所有位置都会移动,直接破坏了顶堆的特性,那重新排序的时间复杂度就变成O(n),如果把头尾互换,然后删掉尾部,那么只会破坏最头部的最大值顺序,可以只针对顶部元素去做排序,时间复杂度就是O(logn).

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
class MaxStack {
private stack: number[];
constructor() {
this.stack = [];
}

push(val: number) {
this.stack.push(val);
this.sortFromBottom();
}

sortFromBottom() {
let currentIndex = this.getLastIndex();
while (true) {
let parentIndex = this.getParentIndex(currentIndex);
if (currentIndex < 0 || parentIndex < 0 || this.stack[parentIndex] > this.stack[currentIndex]) {
break;
} else {
this.swap(parentIndex, currentIndex);
currentIndex = parentIndex;
}
}
}

pop() {
this.swap(0, this.getLastIndex());
const popVal = this.stack.pop();
this.sortFromTop();
return popVal;
}

sortFromTop() {
let currentIndex = 0;
while (true) {
let leftIndex = this.getLeftIndex(currentIndex);
if(leftIndex>this.getLastIndex()) break; //如果没有左节点直接退出
let rightIndex = this.getRightIndex(currentIndex);
let leftVal = this.stack[leftIndex];
let rightVal = this.stack[rightIndex];
let currentVal = this.stack[currentIndex];
if (currentIndex == this.getLastIndex() || (currentVal > rightVal && currentVal > leftVal)) break;
let maxInd = leftVal > rightVal ? leftIndex : rightIndex;
if (leftIndex > this.getLastIndex() || rightIndex > this.getLastIndex() || this.stack[maxInd] <= currentVal) break;
else {
this.swap(maxInd, currentIndex);
currentIndex = maxInd;
}
}
}

getLastIndex(): number {
return this.stack.length - 1;
}

getParentIndex(i: number): number {
return Math.floor((i - 1) / 2);
}

getLeftIndex(i: number): number {
return 2 * i + 1;
}
getRightIndex(i: number): number {
return 2 * i + 2;
}

swap(index1: number, index2: number) {
let temp = this.stack[index1];
this.stack[index1] = this.stack[index2];
this.stack[index2] = temp;
}
peak(): number {
return this.stack[0];
}
print() {
return [...this.stack];
}
}


let a = new MaxStack();
a.push(4)
a.push(14)
a.push(8)
a.push(16)
a.push(24)
a.push(9)
a.push(11)
a.push(15)
a.push(34)
a.push(26)
console.log(a.print());

a.pop();
console.log(a.print());

One Artile to understand call/apply/bind

[Musing]
今天发生了一个小插曲,我有 5 件一样的短袖,突然有一件摸上去手感变得有些生硬,我想🤔了想原因,发现是我国庆住外面,把这件衣服放在太阳下晒了,这还真给我上了一课.我顺便又看了看很多有关材料方面的知识,输出一下巩固记忆
我的个人夏天的衣物需求是:透气,吸汗,凉感,尺寸合适
我有点选择狂,理工男买东西就喜欢横向对比,sku 一多就晕,最近发现蕉下这个品牌很不错,SKU 清晰,定位户外运动,然后很多细节都超棒,比如他的这个衣服标签可以简单的撕掉,logo 也没有特别的跳色,跟衣物基本融为一体,上身的凉感很到位,很像丝绸的感觉

🧵 锦纶 vs 涤纶 对比表

特性 锦纶(Nylon) 涤纶(Polyester)
通俗定义 尼龙拉成丝(聚酰胺纤维) 可乐瓶(PET 塑料)拉成丝(聚酯纤维)
主要特性 吸湿性较强、耐磨度极高、手感柔软、但不抗晒、快干速度中等 吸湿性极弱、抗晒性强、略硬挺、快干能力极佳
触感体验 柔滑、贴肤、有“冰感”,穿着舒适但易发黄 干爽、挺括,稍硬,轻盈不贴身
典型应用 登山裤、冲锋衣、风衣、背包布料 快干衣、运动外套、防晒服、帐篷布、睡袋外层
使用建议 / 场景 穿着体感更舒服,因为有冰凉感,丝绸感,适合户外裤、工装裤、防风衣;避免长时间暴晒,可烘干但易老化 适合运动服、防晒衣、T 恤;耐晒、轻薄、速干、好打理

我觉得主要差别就在穿着体会上,锦纶的体感更舒服,但是不防晒是个问题,容易老化.非贴身衣物其实涤纶的优势就更好了,因为吸水性极差,更适合做防水外层的衣物.总结就是锦纶适合贴身,涤纶适合外套

好了今天 topic 是手写 deepclone,肘起…好的写了半天的 deepclone 感觉没啥意思,主要是分清不同数据类型,然后挨个做处理,今天的话题就改成 call/apply 和 bind 的作用还有关系

在日常的开发中其实不常去使用这三个函数,主要的话在看这几个方法调用的过程中,理解下 this 和一些 ts 的语法关系依赖

先用表格来展示基本概念:

函数名 功能 是否立即执行 参数传递
call 调用函数,并指定 this 指针 是 ✅ a,b,c,d…
apply 调用函数,并指定 this 指针 是 ✅ [a,b,c,d,…]
bind 创建新函数,this 指针被永久绑定 否 ❌ a,b,c,d…
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
function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}

const person = { name: "Yun" };

greet.call(person, "Hello", "!");

greet.apply(person, ["Hi", "😊"]); // the same with apply function, but the parameter need to transfer as array

const sayHi = greet.bind(person, "Hey"); // bind will return with a new function which bind with this
sayHi("!!!");

//here is how call works
Function.prototype.call = function(thisArg, ...args) {
// 1. 将函数作为 thisArg 的属性临时挂载
const fn = Symbol();
thisArg = thisArg ?? globalThis; // 若传入 null/undefined,则绑定到全局对象
thisArg[fn] = this; // this 指向当前函数本身

// 2. 执行函数
const result = thisArg[fn](...args);

// 3. 删除临时属性
delete thisArg[fn];

// 4. 返回结果
return result;
}


//here is how apply works
Function.prototype.apply = function(thisArg, argsArray) {
const fn = Symbol();
thisArg = thisArg ?? globalThis;
thisArg[fn] = this;

const result = thisArg[fn](...(argsArray || []));

delete thisArg[fn];
return result;
}

//here is how bind works
Function.prototype.bind = function(thisArg, ...boundArgs) {
const targetFn = this; // 原函数
return function boundFunction(...args) {
// 当 boundFunction 被调用时,再执行目标函数
return targetFn.apply(thisArg, [...boundArgs, ...args]);
}
}


Output:


总结:

  • call/apply:临时借用 this(立即用完就释放)
  • bind:提前固定 this(永久绑定,延迟使用)

Roll your own Promise(3)

[MUSINGS]
今天又是学会拒绝的一课,拒绝了晚饭邀约,嘻嘻,回家洗完澡吃了宵夜直接变废人,早起写race和all写了快一个小时。总算倒腾完了,先贴出来吧

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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
type State = "pending" | "fulfilled" | "rejected";
class PromiseI {
private state: State;
private fnList: Function[];
private val: any;
constructor(executer: (acc, rej) => void) {
this.state = "pending";
this.fnList = [];
executer(this.resolve.bind(this), this.reject.bind(this))
}

resolve(val: string) {
if (this.state == "pending") {
this.val = val;
this.state = "fulfilled";
console.log("this is resolve:", val);
if (this.fnList.length > 0) {
for (const fn of this.fnList) {
queueMicrotask(() => fn());
}
}
} else {
console.error("can't update the state");
}
}

reject(val: string) {
if (this.state == "pending") {
this.state = "rejected";
this.val = val;
console.log("this is reject:", val);
if (this.fnList.length > 0) {
for (const fn of this.fnList) {
queueMicrotask(() => fn());
}
}
} else {
console.error("can't update the state");
}
}

then(onFulfilled: Function, onRejected?: Function) {
return new PromiseI((resolve, reject) => {
const run = () => {
queueMicrotask(() => {
if (this.state == 'fulfilled') {
try {
const x = onFulfilled(this.val);
resolve(x);
}
catch (e) {
reject(e);
}
}
else if (this.state == 'rejected' && onRejected) {
try {
const x = onRejected(this.val);
resolve(x);
} catch (e) {
reject(e)
}
}
})
}
if (this.state == "pending") {
this.fnList.push(run);
} else {
run();
}
});
}

// static resolve(fn: Function): {

// }

static all(fnList: PromiseI[]) {
return new PromiseI((resolve, reject) => {
let resultList: any = [];
fnList.forEach((fn, i) => {
fn.then(//使用异步来获取
(val) => {
if (val) {
resultList[i] = val;
}
if (resultList.length == fnList.length) {//根据长度判断
resolve(resultList);
}
},
(err) => {
reject(err);
})
})

})
}
static race(promisList: PromiseI[]) {
let flag = false;
return new PromiseI(
(resolve, reject) => {
for (const pro of promisList) {
pro.then(
(val) => {
if (flag) return; //注意promise本身的逻辑,需要在任何一个promise settle的时候立刻返回,所以需要用标志位控制
resolve(val);
flag = true;
}, err => {
reject(err);
}
)
}
}

)
}

}
//test case

// let promise = new PromiseI((resolve, reject) => {
// resolve("success");
// });
// promise.then((res) => {
// console.log("then", res);
// return "yes"
// });
// promise.then((res) => {
// console.log("then", res);
// return "lala"
// });
// setTimeout(() => console.log("timeout 0"), 0);

PromiseI.race([
new PromiseI((resolve) => setTimeout(() => { resolve(111) }, 1000)),
new PromiseI((resolve) => resolve(222))
]
).then(
(arg) => { console.log("success", arg) },
erro => console.log("failed")
)


写于 08:32, Sun, Oct 5, 2025 (GMT+8)