OOP编程基础
Go中的类与对象
Go中用结构体模拟类与对象
1 2 3 4 5 6 7 8 9 10
| 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 }
type Product1 struct { name string }
func (p1 *Product1) SetName(name string) { p1.name = name }
func (p1 *Product1) GetName() string { return "产品1的name:" + p1.name }
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"
type Cache interface { Set(key, value string) Get(key string) string }
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] }
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 )
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 }
type Product1 struct { name string }
func (p *Product1) SetName(name string) { p.name = name }
func (p *Product1) GetName() string { return "产品1的名称:" + p.name }
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" )
type Cache interface { Set(key string, value interface{}) Get(key string) (interface{}, error) }
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") }
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 = &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() }
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" )
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"
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() }
|
总结
- 优点
- 提供了对 “开闭原则”的完美支持。
- 避免使用多重条件转移语句。
- 提供了管理相关的算法族的办法。
- 缺点:
- 客户端必须知道所有的策略类。
- 策略模式将造成产生很多策略类。
- 适合场景
- 需要动态地在几种算法中选择一种。
- 多个类区别仅在于它们的行为或算法不同的场景。