Skip to content

Commit 602327f

Browse files
committed
add factory&blog
1 parent 6a2e76c commit 602327f

6 files changed

+392
-12
lines changed
+357-8
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,364 @@
1-
**版权归作者所有,请勿抄袭**
1+
## 这些Coding套路你不会还不知道吧?
22

3-
## 聊聊Go开发中常用的设计模式
3+
对于一名程序员来说,编码进阶是成为优秀工程师非常重要的一步,它可以让我们更加熟练地掌握编程,深入理解数据结构和算法,从而更好地完成复杂的任务,提高工作效率。而我认为熟练使用设计模式就是编码进阶的最好方式之一,下面就用一篇文章来分享下Go开发中常用的设计模式。
44

5-
- 全局单一实例:单例模式
5+
- **:one:全局单一实例:单例模式**
66

7-
- 对象流水线化:工厂模式
7+
- **:car:获取对象超简单:工厂模式**
88

9-
- 重复代码太多?试试:模板方法模式
9+
- **:clipboard:重复代码太多?试试:模板方法模式**
1010

11-
- 代码接口太多不知道怎么维护?这里有:策略模式
11+
- **:ghost:接口对应功能不知道怎么维护?这里有:策略模式**
1212

13-
- 给你的对象多加几件衣服:装饰器模式
13+
- **:balloon:独特好玩的Functional Options模式**
1414

15-
### 为什么要有设计模式
15+
### What?Why?
16+
17+
从两个问题开始今天的分享:
18+
19+
**设计模式是什么?**
20+
21+
简单来讲,就两个字:**`套路`**,编码的套路,在我们写代码的时候这个套路可以不用,但是不能不知道。
22+
23+
**为什么要使用设计模式?**
24+
25+
**设计模式是一种被反复使用的,针对软件设计中常见问题的可复用解决方案。它们提供了一种经过验证的方式来解决复杂的设计问题,并帮助开发人员编写更加清晰、可维护和可扩展的代码**
26+
27+
使用设计模式的好处包括:
28+
29+
1. 提高代码的可读性和可维护性。通过使用设计模式,开发人员可以将复杂的问题分解成更小、更易于管理的部分,并且可以将这些部分组织成一致的、易于理解的代码结构。
30+
2. 提高代码的可重用性。设计模式提供了一种标准化的解决方案,可以让相同的功能代码在多个地方重复使用,从而避免了重复编写相同的代码,减少了开发时间和成本。
31+
3. 提高代码的灵活性和扩展性。设计模式允许开发人员在不改变现有代码的情况下,轻松地添加新的功能或修改现有的功能。
32+
33+
### 常见的设计模式实践
34+
35+
#### 1 全局单一实例:单例模式
36+
37+
首先,单例模式一般用于以下业务场景:
38+
39+
(1)整个程序的运行中只允许有一个类的实例。
40+
41+
(2)创建对象时耗时过多或者耗资源过多,但又经常用到的。
42+
43+
(3)需要全局访问并保证线程安全的对象。
44+
45+
(4)需要保持状态的对象。
46+
47+
具体场景:
48+
49+
数据库连接对象。我们在具体的项目中往往每种数据库驱动只会使用它的一个实例,因此在这里我们就能够使用单例模式。
50+
51+
代码:
52+
53+
```go
54+
import "sync"
55+
56+
type MysqlConn struct {
57+
Addr string
58+
}
59+
60+
var (
61+
mysqlConn *MysqlConn
62+
once = sync.Once{}
63+
)
64+
65+
func GetMySQLConn() *MysqlConn {
66+
once.Do(func() {
67+
mysqlConn = &MysqlConn{Addr: "127.0.0.1"}
68+
})
69+
return mysqlConn
70+
}
71+
```
72+
73+
#### 2 获取对象超简单:工厂模式
74+
75+
工厂模式通常应用于以下场景:
76+
77+
(1)当一个系统需要灵活地配置一组对象,并且需要动态地选择其中的一个时。
78+
79+
(2)当一个类需要频繁地创建和销毁时,可以使用工厂模式来提高性能。
80+
81+
具体场景:
82+
83+
简单来讲就是想要获取一个实例,但是这个实例的属性可能会随着参数的变化而具有不同的形态,还可以用数据库连接来举例,我们有多个服务器可以进行连接,但是每次只能连接一个,而且连接的时候可以填写自己的认证账密。
84+
85+
代码:
86+
87+
```go
88+
type DB struct {
89+
Addr, UserName, Passwd string
90+
}
91+
92+
func NewMysqlConn(addr, userName, passwd string) *DB {
93+
return &DB{
94+
Addr: addr,
95+
UserName: userName,
96+
Passwd: passwd,
97+
}
98+
}
99+
```
100+
101+
#### 3 重复代码太多?试试:模板方法模式
102+
103+
模板方法模式通常应用于以下场景:
104+
105+
(1)当一个算法的步骤中有一部分是不变的,而另一部分是需要根据不同的条件进行变化时,可以使用模板方法模式来实现。
106+
107+
(2)当一个类的子类需要实现一个接口的不同版本时,可以使用模板方法模式来实现。
108+
109+
具体场景:
110+
111+
[做水果酸奶](https://mp.weixin.qq.com/s?__biz=MzIxNDc2ODc3MA==&mid=2247485000&idx=1&sn=c1e46cfbf817aea90398b57b4630895d&chksm=97a3cba5a0d442b3fcb9bb2814d38612c540dcdda0cdbd9d18f783b87781e64b7897af708c6d#rd),水果或其他食材是统一的抽象,而火龙果、芒果、饼干都是具体,因为不管用什么水果或食材来做,做法都是如出一辙的,所以做法也是一个抽象,这就好比一个模板,对,就是模版方法模式。
112+
113+
代码:
114+
115+
```go
116+
import (
117+
"fmt"
118+
"testing"
119+
)
120+
121+
type MakeYogurtTemplate interface {
122+
CreateYogurt() //准备好酸奶
123+
CutFruit() //切水果
124+
Merge() //放在一起搅拌
125+
Optimize() //调制味道
126+
Eating() //开吃
127+
Do()
128+
}
129+
130+
type DragonFruit struct {
131+
}
132+
133+
func (d *DragonFruit) CreateYogurt() {
134+
fmt.Println("用鲜奶和乳酸菌发酵好酸奶")
135+
}
136+
137+
func (d *DragonFruit) CutFruit() {
138+
fmt.Println("把火龙果切成块块")
139+
}
140+
141+
func (d *DragonFruit) Merge() {
142+
fmt.Println("放在一起进行搅拌")
143+
}
144+
145+
func (d *DragonFruit) Optimize() {
146+
fmt.Println("调制出自己喜欢的味道")
147+
}
148+
149+
func (d *DragonFruit) Eating() {
150+
fmt.Println("开吃")
151+
}
152+
153+
func (d *DragonFruit) Do() {
154+
d.CreateYogurt()
155+
d.CutFruit()
156+
d.Merge()
157+
d.Optimize()
158+
d.Eating()
159+
}
160+
161+
func TestDragonFruit(t *testing.T) {
162+
d := &DragonFruit{}
163+
d.Do()
164+
}
165+
166+
type Mango struct {
167+
}
168+
169+
func (m *Mango) CreateYogurt() {
170+
fmt.Println("用鲜奶和乳酸菌发酵好酸奶")
171+
}
172+
173+
func (m *Mango) CutFruit() {
174+
fmt.Println("把芒果切成块块")
175+
}
176+
177+
func (m *Mango) Merge() {
178+
fmt.Println("放在一起进行搅拌")
179+
}
180+
181+
func (m *Mango) Optimize() {
182+
fmt.Println("调制出自己喜欢的味道")
183+
}
184+
185+
func (m *Mango) Eating() {
186+
fmt.Println("开吃")
187+
}
188+
189+
func (m *Mango) Do() {
190+
m.CreateYogurt()
191+
m.CutFruit()
192+
m.Merge()
193+
m.Optimize()
194+
m.Eating()
195+
}
196+
197+
func TestMango(t *testing.T) {
198+
m := &Mango{}
199+
m.Do()
200+
}
201+
```
202+
203+
#### 4 接口对应功能不知道怎么维护?这里有:策略模式
204+
205+
策略模式通常在一个系统中需要多个算法,并且这些算法需要在不同的时间或条件下使用不同的算法时,可以使用策略模式来实现。
206+
207+
具体场景:
208+
209+
对于新手程序员同学经常会有这样的选择,到底是学Go还是学Java,这显然是两种不同的策略选择,但是不管是学习哪一个的基本流程都是差不多的,比如都是先准备学习资料,然后在学习和实践,因此可以基于此使用策略模式来形容。
210+
211+
代码:
212+
213+
策略抽象
214+
215+
```go
216+
type Learn interface {
217+
PrepareData()
218+
DoLearn()
219+
Play()
220+
}
221+
222+
func LearnLang(l Learn) {
223+
l.PrepareData()
224+
l.DoLearn()
225+
l.Play()
226+
}
227+
```
228+
229+
策略1
230+
231+
```go
232+
import "fmt"
233+
234+
type LikeGo struct {
235+
}
236+
237+
func (g *LikeGo) PrepareData() {
238+
fmt.Println("准备Go资料")
239+
}
240+
241+
func (g *LikeGo) DoLearn() {
242+
fmt.Println("学习Go")
243+
}
244+
245+
func (g *LikeGo) Play() {
246+
fmt.Println("玩转Go语言")
247+
}
248+
```
249+
250+
策略2
251+
252+
```go
253+
import "fmt"
254+
255+
type LikeJava struct {
256+
}
257+
258+
func (g *LikeJava) PrepareData() {
259+
fmt.Println("准备Java资料")
260+
}
261+
262+
func (g *LikeJava) DoLearn() {
263+
fmt.Println("学习Java")
264+
}
265+
266+
func (g *LikeJava) Play() {
267+
fmt.Println("玩Java")
268+
}
269+
```
270+
271+
执行策略
272+
273+
```go
274+
import "testing"
275+
276+
func TestLearn(t *testing.T) {
277+
likeGo := &LikeGo{}
278+
LearnLang(likeGo)
279+
}
280+
```
281+
282+
#### 5 独特好玩的Functional Options模式
283+
284+
Functional Options模式通常在当一个对象或函数具有多个参数时,这些参数可能会有不同的默认值或取值范围。通过将它们作为函数的选项传递,可以更灵活地控制函数的行为
285+
286+
具体场景:
287+
288+
gRPC服务进行实例化的时候有些参数可以不同填,即选填,之后在源码内部会使用默认的值,于是我们就可以使用以下的方式进行处理。
289+
290+
代码:
291+
292+
```go
293+
import "time"
294+
295+
type RpcServer struct {
296+
Name string
297+
MaxConn int
298+
Address []string
299+
TimeOut time.Duration
300+
}
301+
302+
type RpcServerOption func(server *RpcServer)
303+
304+
func WithName(name string) RpcServerOption {
305+
return func(server *RpcServer) {
306+
server.Name = name
307+
}
308+
}
309+
310+
func WithMaxConn(max int) RpcServerOption {
311+
return func(server *RpcServer) {
312+
server.MaxConn = max
313+
}
314+
}
315+
316+
func WithAddress(addr []string) RpcServerOption {
317+
return func(server *RpcServer) {
318+
server.Address = addr
319+
}
320+
}
321+
322+
func WithTimeOut(timeout time.Duration) RpcServerOption {
323+
return func(server *RpcServer) {
324+
server.TimeOut = timeout
325+
}
326+
}
327+
328+
func NewRpcServer(opts ...RpcServerOption) *RpcServer {
329+
server := &RpcServer{}
330+
331+
for _, opt := range opts {
332+
opt(server)
333+
}
334+
335+
return server
336+
}
337+
```
338+
339+
实例化:
340+
341+
```go
342+
import (
343+
"fmt"
344+
"testing"
345+
"time"
346+
)
347+
348+
func TestCreateRpcServerByOptions(t *testing.T) {
349+
rpcServer := NewRpcServer(
350+
WithAddress([]string{"127.0.0.1"}),
351+
WithName("rpcServer"),
352+
WithMaxConn(1),
353+
WithTimeOut(time.Second),
354+
)
355+
356+
fmt.Println(*rpcServer)
357+
}
358+
```
359+
360+
### 小总结
361+
362+
本文主要介绍了Go开发中常用的设计模式,包括全局单一实例:单例模式、工厂模式、模板方法模式、策略模式和Functional Options模式。这些设计模式可以帮助我们更好地组织代码,提高代码的可读性和可重用性。
363+
364+
总之,掌握这些设计模式对于提高Go程序员的编码能力非常有帮助,可以让我们在编写代码时更加得心应手,同时也能提高代码的质量和可维护性。

code/05-factory/db_conn.go

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package _6_once
2+
3+
type DB struct {
4+
Addr, UserName, Passwd string
5+
}
6+
7+
func NewMysqlConn(addr, userName, passwd string) *DB {
8+
return &DB{
9+
Addr: addr,
10+
UserName: userName,
11+
Passwd: passwd,
12+
}
13+
}

code/05-factory/db_conn_test.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package _6_once
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
)
7+
8+
func TestNewMysqlConn(t *testing.T) {
9+
conn := NewMysqlConn()
10+
fmt.Println(conn)
11+
}

0 commit comments

Comments
 (0)