|
1 |
| -**版权归作者所有,请勿抄袭** |
| 1 | +## 这些Coding套路你不会还不知道吧? |
2 | 2 |
|
3 |
| -## 聊聊Go开发中常用的设计模式 |
| 3 | +对于一名程序员来说,编码进阶是成为优秀工程师非常重要的一步,它可以让我们更加熟练地掌握编程,深入理解数据结构和算法,从而更好地完成复杂的任务,提高工作效率。而我认为熟练使用设计模式就是编码进阶的最好方式之一,下面就用一篇文章来分享下Go开发中常用的设计模式。 |
4 | 4 |
|
5 |
| -- 全局单一实例:单例模式 |
| 5 | +- **:one:全局单一实例:单例模式** |
6 | 6 |
|
7 |
| -- 对象流水线化:工厂模式 |
| 7 | +- **:car:获取对象超简单:工厂模式** |
8 | 8 |
|
9 |
| -- 重复代码太多?试试:模板方法模式 |
| 9 | +- **:clipboard:重复代码太多?试试:模板方法模式** |
10 | 10 |
|
11 |
| -- 代码接口太多不知道怎么维护?这里有:策略模式 |
| 11 | +- **:ghost:接口对应功能不知道怎么维护?这里有:策略模式** |
12 | 12 |
|
13 |
| -- 给你的对象多加几件衣服:装饰器模式 |
| 13 | +- **:balloon:独特好玩的Functional Options模式** |
14 | 14 |
|
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程序员的编码能力非常有帮助,可以让我们在编写代码时更加得心应手,同时也能提高代码的质量和可维护性。 |
0 commit comments