0%

常见设计模式和设计原则

记录一下常见的设计模式和设计原则。

1. 设计模式

1.1 装饰器模式

  1. 装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。它主要的作用是给原始类添加增强功能。

  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
package decorator

// IDraw IDraw
type IDraw interface {
Draw() string
}

// Square 正方形
type Square struct{}
func (s Square) Draw() string {
return "this is a square"
}

// ColorSquare 有颜色的正方形
type ColorSquare struct {
square IDraw
color string
}
func (c ColorSquare) Draw() string {
return c.square.Draw() + ", color is " + c.color
}

func NewColorSquare(square IDraw, color string) ColorSquare {
return ColorSquare{color: color, square: square}
}




func TestColorSquare_Draw(t *testing.T) {
sq := Square{}
csq := NewColorSquare(sq, "red")
got := csq.Draw()
assert.Equal(t, "this is a square, color is red", got)
// this is a square, color is red
}

1.2 适配器模式

  1. 这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。
  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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package adapter

import "fmt"

// AWSClient aws sdk
type AWSClient struct{}
// RunInstance 启动实例
func (c *AWSClient) RunInstance(cpu, mem float64) error {
fmt.Printf("aws client run success, cpu: %f, mem: %f", cpu, mem)
return nil
}
// AliyunClient aliyun sdk
type AliyunClient struct{}
// CreateServer 启动实例
func (c *AliyunClient) CreateServer(cpu, mem int) error {
fmt.Printf("aws client run success, cpu: %d, mem: %d", cpu, mem)
return nil
}


// ICreateServer 创建云主机
type ICreateServer interface {
CreateServer(cpu, mem float64) error
}

// 自己做一个 AwsClientAdapter 适配器
type AwsClientAdapter struct {
Client AWSClient
}
// CreateServer 启动实例
func (a *AwsClientAdapter) CreateServer(cpu, mem float64) error {
a.Client.RunInstance(cpu, mem)
return nil
}
// AliyunClientAdapter 适配器
type AliyunClientAdapter struct {
Client AliyunClient
}
// CreateServer 启动实例
func (a *AliyunClientAdapter) CreateServer(cpu, mem float64) error {
a.Client.CreateServer(int(cpu), int(mem))
return nil
}




func TestAliyunClientAdapter_CreateServer(t *testing.T) {
// 确保 adapter 实现了目标接口
var a ICreateServer = &AliyunClientAdapter{
Client: AliyunClient{},
}
a.CreateServer(1.0, 2.0)
}

func TestAwsClientAdapter_CreateServer(t *testing.T) {
// 确保 adapter 实现了目标接口
var a ICreateServer = &AwsClientAdapter{
Client: AWSClient{},
}
a.CreateServer(1.0, 2.0)
}

1.3 策略模式

  1. 定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。
  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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package strategy

import (
"fmt"
"io/ioutil"
"os"
)

// StorageStrategy 存储策略
type StorageStrategy interface {
Save(name string, data []byte) error
}

var strategys = map[string]StorageStrategy{
"file": &fileStorage{},
"encrypt_file": &encryptFileStorage{},
}
func NewStorageStrategy(t string) (StorageStrategy, error) {
s, ok := strategys[t]
if !ok {
return nil, fmt.Errorf("not found StorageStrategy: %s", t)
}
return s, nil
}

// 保存到文件策略
type fileStorage struct{}
func (s *fileStorage) Save(name string, data []byte) error {
return ioutil.WriteFile(name, data, os.ModeAppend)
}

// 加密保存到文件策略
type encryptFileStorage struct{}
func (s *encryptFileStorage) Save(name string, data []byte) error {
data, err := encrypt(data)
if err != nil {
return err
}
return ioutil.WriteFile(name, data, os.ModeAppend)
}

func encrypt(data []byte) ([]byte, error) {
return data, nil
}




// 传递不同的对象进去,调用相同的接口方法
func Test_demo(t *testing.T) {
// 假设这里获取数据,以及数据是否敏感
data, sensitive := getData()
strategyType := "file"
if sensitive {
strategyType = "encrypt_file"
}

storage, err := NewStorageStrategy(strategyType)
assert.NoError(t, err)
assert.NoError(t, storage.Save("./test.txt", data))
}

2. 设计原则

2.1 单一职责原则

指一个类或者模块应该有且只有一个改变的原因。

如果一个类承担的职责过多,就等于把这些职责耦合在一起了。一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当发生变化时,设计会遭受到意想不到的破坏。而如果想要避免这种现象的发生,就要尽可能的遵守单一职责原则。此原则的核心就是解耦和增强内聚性。

2.2 开闭原则

当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。

在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。

2.3 依赖倒转原则

程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

  • 高层模块不应该依赖低层模块,高层模块和低层模块都应该依赖于抽象。
  • 抽象不应该依赖于具体,具体应该依赖于抽象。

举例说明:

封装数据库操作可认为低层模块,而处理业务逻辑可认为高层模块,那么如果处理业务逻辑需要等到封装数据库操作的代码写完的话才能添加的话讲会严重拖垮项目的进度。

正确的做法应该是处理业务逻辑的程序员提供一个封装好数据库操作的抽象接口,交给低层模块的程序员去编写,这样双方可以单独编写而互不影响。

3. 参考资料

可以加首页作者微信,咨询相关问题!