OOP编程基础

Go中的类与对象

Go中用结构体模拟类与对象

1
2
3
4
5
6
7
8
9
10
// Bike 表示一个类型或者说一个OOP类
type Bike struct {
color string // 首字母小写表示属性私有
Name string // 首字母大小表示属性公有
}

// 首字母大写表示方法对外公开
func (b *Bike) Move() string {
return b.color + "的自行车在前进🚲"
}

Go中的三大基本特性

封装:首字母大小写方式代表公有私有权限

1
2
3
4
5
6
7
type Person struct {
name string
}

func (p *Person) Eat() {
fmt.Println(p.name + "在吃饭")
}

继承:使用内嵌的方式,对结构体struct进行组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Person struct {
name string
}

func (p *Person) Eat() {
fmt.Println(p.name + "在吃饭")
}

type Chinese struct {
Person
skin string
}

func (c *Chinese) GetSkin() string {
return c.skin
}

多态:使用interface来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Human interface {
Speck()
}

type American struct {
name string
Language string
}

func (a *American) Speck() {
fmt.Println("美国人" + a.name + "说" + a.Language)
}

type Chinese struct {
name string
Language string
}

func (c *Chinese) Speck() {
fmt.Println("中国人" + a.name + "说" + a.Language)
}

设计模式的五大原则

  • 单一功能原则

对象应该具有单一功能。比如说人类,不能将走路跳跃定义为一个方法。

  • 开闭原则

类或函数,扩展我们是开放的,修改我们是要关闭的。

  • 里氏替换原则

子类对象可以替换父类对象,而程序逻辑不变。在Go语言中要面向接口去开发,接口是很好去替换的。

  • 接口隔离原则

就是要保证接口的最小化,接口不能滥用,保持接口定义的合理性,需要根据业务来划分接口。

  • 依赖反转原则

就是不要依赖具体的实现,要去面向接口去开发

单一功能原则和开闭原则示例

Tom的设计

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
type Bike struct{}

func (b *Bike) Move() {
fmt.Println("骑自行车")
}

type Car struct{}

func (b *Car) Move() {
fmt.Println("开车")
}

type Person struct {
name string
}

func (p *Person) By(action string) {
switch action {
case "bike":
bike := Bike{}
bike.Move()
case "car":
car := Car{}
car.Move()
}
}

看到上面Tom设计的Person类的By方法了没有,它通过传入action参数,去调用不同交通工具的移动Move方法,其实这样的设计是违反了单一功能原则,就是这个By方法它有两层意思,也违反了开闭原则,就是说我要增加一个交通工具如地铁,还要再增加一个case,可以通过下面Kitty的设计来避免

kitty的设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type Bike struct{}

func (b *Bike) Move() {
fmt.Println("骑自行车")
}

type Car struct{}

func (b *Car) Move() {
fmt.Println("开车")
}

type Person struct {
name string
}

func (p *Person) RideBike(bike Bike) {
bike.Move()
}

func (p *Person) DriveCar(car Car) {
car.Move()
}

Kitty的设计就很好,它是将一个人是开车还是骑自行车的交通方式类型方法定义拆分开了,这样就可以很好的扩展,比如说我要为这个Person类再添加一个坐地铁方式,是不是直接再添加一个方法就可以了,而不用再去修改By方法了,对吧,这样就满足了单一原则以及开闭原则

依赖反转原则示例

Tom的设计

1
2
3
4
5
6
7
8
9
10
11
12
13
type V6Engine struct{}

func (v6 *V6Engine) Run() string {
return "V6Engine run"
}

type BMWCar struct {
engine V6Engine
}

func (bmw *BMWCar) RunEngine() string {
return "BMWCar的" + bmw.engine.Run()
}

Tom的设计其实违反了依赖反转原则,它依赖具体的实现,这么说吧,现在这个宝马汽车需要将V6引擎升级为V8引擎,你是不是还要修改V6Engine类型到V8Engine类型,可以通过下面的方式去避免

Kitty的设计

1
2
3
4
5
6
7
8
9
10
11
type Engine interface {
Run() string
}

type BMWCar struct {
engine Engine
}

func (bmw *BMWCar) RunEngine() string {
return "BMWCar的" + bmw.engine.Run()
}

Kitty的设计就很好,将汽车引擎定义成接口,就避免了依赖具体的实现,比如说上面的宝马汽车之前是V6引擎,现在要升级为V8引擎,那这里的代码其实不用改动了,添加一个V8引擎并将Engine接口实现即可

创建型模式

简单工厂模式

概念

又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一

在简单工厂模式中,可以根据参数的不同返回不同类的实例

简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类

结构

代码推演

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

// 实现一个抽象的产品
type Product interface {
SetName(name string)
GetName() string
}

// 实现具体的产品:产品1
type Product1 struct {
name string
}

func (p1 *Product1) SetName(name string) {
p1.name = name
}

func (p1 *Product1) GetName() string {
return "产品1的name:" + p1.name
}

// 实现具体的产品:产品2
type Product2 struct {
name string
}

func (p1 *Product2) SetName(name string) {
p1.name = name
}

func (p2 *Product2) GetName() string {
return "产品2的name:" + p2.name
}

type productType int

const (
p1 productType = iota
p2
)

// 实现简单工厂类
type productFactory struct {
}

func (pf productFactory) Create(productType productType) Product {
if productType == p1 {
return &Product1{}
}
if productType == p2 {
return &Product2{}
}
return nil
}

测试:

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

import (
"fmt"
"testing"
)

func TestProduct_Create(t *testing.T) {
product1 := &Product1{}
product1.SetName("p1")
fmt.Println(product1.GetName())

product2 := &Product1{}
product2.SetName("p2")
fmt.Println(product2.GetName())
}

func TestProductFactory_Create(t *testing.T) {
productFactory := productFactory{}
p1 := productFactory.Create(p1)
p1.SetName("p1")
fmt.Println(p1.GetName())

p2 := productFactory.Create(p2)
p2.SetName("p2")
fmt.Println(p2.GetName())

}

实战模拟

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

import "errors"

// 定义一个Cache接口,作为父类
type Cache interface {
Set(key, value string)
Get(key string) string
}

// 实现具体的Cache:RedisCache
type RedisCache struct {
data map[string]string
}

func (redis *RedisCache) Set(key, value string) {
redis.data[key] = value
}

func (redis *RedisCache) Get(key string) string {
return "redis:" + redis.data[key]
}

// 实现具体的Cache:MemCache
type MemCache struct {
data map[string]string
}

func (mem *MemCache) Set(key, value string) {
mem.data[key] = value
}

func (mem *MemCache) Get(key string) string {
return "mem:" + mem.data[key]
}

type cacheType int

const (
redis cacheType = iota
mem
)

// 实现Cache的简单工厂
type CacheFactory struct {
}

func (factory *CacheFactory) Create(cacheType cacheType) (Cache, error) {
if cacheType == redis {
return &RedisCache{
data: map[string]string{},
}, nil
}
if cacheType == mem {
return &MemCache{
data: map[string]string{},
}, nil
}
return nil, errors.New("error cache type")
}

测试:

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

import (
"fmt"
"testing"
)

func TestCacheFactory_Create(t *testing.T) {
cacheFactory := &CacheFactory{}
redis, err := cacheFactory.Create(redis)
if err != nil {
t.Error(err)
}

redis.Set("k1", "v1")
fmt.Println(redis.Get("k1"))

mem, err := cacheFactory.Create(mem)
if err != nil {
t.Error(err)
}
mem.Set("k1", "v1")

fmt.Println(mem.Get("k1"))
}

总结

  • 优点:面向接口的开发,实现了解耦。
  • 缺点:违背了 开闭原则
  • 适合:创建的对象比较少的情况。

工厂模式

概念

  • 工厂方法模式又称为工厂模式,也叫虚拟构造器模式或者多态工厂模式,它属于类创建型模式
  • 在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象

结构

代码推演

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

// 定义一个抽象的产品
type Product interface {
SetName(name string)
GetName() string
}

// 实现具体的产品:产品1
type Product1 struct {
name string
}

func (p *Product1) SetName(name string) {
p.name = name
}

func (p *Product1) GetName() string {
return "产品1的名称:" + p.name
}

// 实现具体的产品:产品2
type Product2 struct {
name string
}

func (p *Product2) SetName(name string) {
p.name = name
}

func (p *Product2) GetName() string {
return "产品2的名称:" + p.name
}

// 定义一个抽象的产品工厂
type ProductFactory interface {
Create() Product
}

// 实现具体的产品工厂
type Product1Factory struct{}

func (p *Product1Factory) Create() Product {
return &Product1{}
}

type Product2Factory struct{}

func (p *Product2Factory) Create() Product {
return &Product2{}
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package demo4_3

import (
"fmt"
"testing"
)

func TestProduct1(t *testing.T) {
var productFactory1 ProductFactory

productFactory1 = &Product1Factory{}
p1 := productFactory1.Create()
p1.SetName("p1")

fmt.Println(p1.GetName())
}

实战模拟

工厂方法模式实现一个缓存库,支持set、get方法,同时支持Redis和Memcache

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

import (
"errors"
"sync"
)

// 定义一个抽象的Cache接口
type Cache interface {
Set(key string, value interface{})
Get(key string) (interface{}, error)
}

// 实现具体的Cache: RedisCache
type RedisCache struct {
data map[string]interface{}
mux sync.RWMutex
}

func (r *RedisCache) Set(key string, value interface{}) {
r.mux.Lock()
defer r.mux.Unlock()
r.data[key] = value
return
}

func (r *RedisCache) Get(key string) (interface{}, error) {
r.mux.RLock()
defer r.mux.RUnlock()
if v, ok := r.data[key]; ok {
return v, nil
}
return nil, errors.New("key is not found")
}

// 实现具体的Cache: Memcache
type Memcache struct {
data map[string]interface{}
mux sync.RWMutex
}

func (m *Memcache) Set(key string, value interface{}) {
m.mux.Lock()
defer m.mux.Unlock()
m.data[key] = value
return
}

func (m *Memcache) Get(key string) (interface{}, error) {
m.mux.RLock()
defer m.mux.RUnlock()
if v, ok := m.data[key]; ok {
return v, nil
}
return nil, errors.New("key is not found")
}

// 定义一个抽象的缓存工厂
type CacheFactory interface {
Create() Cache
}

// 实现具体的缓存工厂
type RedisCacheFactory struct{}

func (r *RedisCacheFactory) Create() Cache {
return &RedisCache{data: make(map[string]interface{})}
}

type MemcacheFactory struct{}

func (m *MemcacheFactory) Create() Cache {
return &Memcache{data: make(map[string]interface{})}
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package demo4_4

import (
"fmt"
"testing"
)

func TestRedisCache(t *testing.T) {
var redisCacheFactory CacheFactory
redisCacheFactory = &RedisCacheFactory{}

redisCache := redisCacheFactory.Create()
redisCache.Set("key1", "value1")
fmt.Println(redisCache.Get("key1"))
}

总结

  • 优点:保持了简单工厂模式的优点,而且克服了它的缺点。
  • 缺点:在添加新产品时,在一定程度上增加了系统的复杂度。
  • 适合:客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可。

单例模式

比较简单的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

type WebConfig struct {
Port int
}

var cc *WebConfig

func GetConfig() *WebConfig {
if cc == nil {
cc = &WebConfig{Port: 8080}
}
return cc

}

func main() {
c1 := GetConfig()
c2 := GetConfig()
fmt.Println(c1 == c2)
}

上面是单线程执行的,如果是多线程执行就会线程不安全,可以使用双重检查加锁保证线程安全

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

import (
"fmt"
"sync"
)

type WebConfig struct {
Port int
}

var cc *WebConfig
var once sync.Once

func GetConfig() *WebConfig {
once.Do(func() {
// 如果这个cc有值就不会加锁
cc = &WebConfig{Port: 8080}
})
return cc

}

func main() {
c1 := GetConfig()
c2 := GetConfig()

fmt.Println(c1 == c2)
}

结构型模式

装饰模式

概念

  • 一种动态地往一个类中添加新的行为的设计模式。
  • 就功能而言,修饰模式相比生成子类更为灵活,这样可以给某个对象而不是整个类添加一些功能。
  • 它是一种对象结构型模式

结构

代码推演

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 demo5_1

import "fmt"

// 定义一个抽象的组件
type Component interface {
Operate()
}

// 实现一个具体的组件:组件1
type Component1 struct {
}

func (c1 *Component1) Operate() {
fmt.Println("c1 operate")
}

// 定义一个抽象的装饰者
type Decorator interface {
Component
Do() // 这是一个额外的方法
}

// 实现一个具体的装饰者
type Decorator1 struct {
c Component
}

func (d1 *Decorator1) Do() {
fmt.Println("发生了装饰行为")
}

func (d1 *Decorator1) Operate() {
d1.Do()
d1.c.Operate()
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package demo5_1

import "testing"

func TestComponent1_Operate(t *testing.T) {
c1 := &Component1{}
c1.Operate()
}

func TestDecorator1_Operate(t *testing.T) {
d1 := &Decorator1{}
c1 := &Component1{}
d1.c = c1
d1.Operate()
}

实战模拟

  • 实现HTTP中间件记录请求的URL、方法。
  • 实现HTTP中间件记录请求的网络地址。
  • 实现HTTP中间件记录请求的耗时
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
package main

import (
"io"
"log"
"net/http"
"time"
)

// Step1: 实现一个HTTP Server
// Step2: 实现一个HTTP Handler
// Step3: 实现中间件的功能:
// (1)、实现HTTP中间件记录请求的URL、方法。
// (2)、实现HTTP中间件记录请求的网络地址。
// (3)、实现HTTP中间件记录请求的耗时。

// 记录请求的URL和方法
func tracing(next http.HandlerFunc) http.HandlerFunc {
return func(resp http.ResponseWriter, req *http.Request) {
log.Printf("实现HTTP中间件记录请求的URL和方法:%s, %s", req.URL, req.Method)
next.ServeHTTP(resp, req)
}
}

// 记录请求的网络地址
func logging(next http.HandlerFunc) http.HandlerFunc {
return func(resp http.ResponseWriter, req *http.Request) {
log.Printf("实现HTTP中间件记录请求的网络地址:%s", req.RemoteAddr)
next.ServeHTTP(resp, req)
}
}

// 记录请求的耗时
func processing(next http.HandlerFunc) http.HandlerFunc {
return func(resp http.ResponseWriter, req *http.Request) {
start := time.Now()
next.ServeHTTP(resp, req)
duration := time.Since(start)
log.Printf("实现HTTP中间件记录请求的耗时: %v", duration)
}
}

func HelloHandler(resp http.ResponseWriter, req *http.Request) {
io.WriteString(resp, "hello world")
}

func main() {
http.HandleFunc("/", tracing(logging(processing(HelloHandler))))
log.Printf("starting http server at: %s", "http://127.0.0.1:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}

总结

优点

  • 可以通过一种动态的方式来扩展一个对象的功能。
  • 可以使用多个具体装饰类来装饰同一对象,增加其功能。
  • 具体组件类与具体装饰类可以独立变化,符合”开闭原则”。

缺点:

  • 对于多次装饰的对象,易于出错,排错也很困难。
  • 对于产生很多具体装饰类,增加系统的复杂度以及理解成本。

适合场景:

  • 需要给一个对象增加功能,这些功能可以动态地撤销。
  • 需要给一批兄弟类增加或者改装功能。

行为型模式

策略模式

概念

  • 策略模式作为一种软件设计模式,指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。
  • 比如每个人都要”交个人所得税”,但是”在美国交人个所得税” 和 “在中国交个人所得税”,就有不同的算税方法。
  • 策略模式是一种对象行为型模式。

结构

代码推演

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

import "fmt"

// 实现一个上下文的类
type Context struct {
Strategy
}

// 定义抽象的策略
type Strategy interface {
Do()
}

// 实现具体的策略
type Strategy1 struct{}

func (s1 *Strategy1) Do() {
fmt.Println("执行策略1")
}

type Strategy2 struct{}

func (s2 *Strategy2) Do() {
fmt.Println("执行策略2")
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
package demo6_1

import "testing"

func TestStrategy_Do(t *testing.T) {
context := Context{}
context.Strategy = &Strategy1{}
context.Do()

context.Strategy = &Strategy2{}
context.Do()
}

实战模拟

实现一个日志记录器满足:文件记录和数据库记录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
package demo6_2

import "fmt"

// 实现一个日志记录器(相当于Context)
type LogManager struct {
Logging
}

func NewLogManager(logging Logging) *LogManager {
return &LogManager{logging}
}

// 抽象的日志
type Logging interface {
Info()
Error()
}

// 实现具体的日志:文件方式日志
type FileLogging struct {
}

func (fl *FileLogging) Info() {
fmt.Println("文件记录Info")
}

func (fl *FileLogging) Error() {
fmt.Println("文件记录Error")
}

// 实现具体的日志:数据库方式的日志
type DBLogging struct {
}

func (dl *DBLogging) Info() {
fmt.Println("数据库记录Info")
}

func (fl *DBLogging) Error() {
fmt.Println("数据库记录Error")
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package demo6_2

import "testing"

func TestNewLogManager(t *testing.T) {
fileLogging := &FileLogging{}
logManager := NewLogManager(fileLogging)

logManager.Info()
logManager.Error()

dbLogging := &DBLogging{}
logManager.Logging = dbLogging
logManager.Info()
logManager.Error()
}

总结

  • 优点
    • 提供了对 “开闭原则”的完美支持。
    • 避免使用多重条件转移语句。
    • 提供了管理相关的算法族的办法。
  • 缺点:
    • 客户端必须知道所有的策略类。
    • 策略模式将造成产生很多策略类。
  • 适合场景
    • 需要动态地在几种算法中选择一种。
    • 多个类区别仅在于它们的行为或算法不同的场景。