go设计模式之结构型模式

结构型模式解决什么问题

结构模式关注类和对象的组合,解决如何将类和对象组装成较大结构的同时,保持结构的灵活和可复用性。

1.装饰模式(俄罗斯套娃)

装饰模式是对基类进行包装(装饰)从而为对象增加新功能或改变原有功能,操作对象和装饰器对象由于实现了同一接口,
因而操作对象可以用装饰器进行多次(套娃式)封装,对象将获得所有装饰器作用叠加后的功能。

下面代码描述了如何通过层层装饰返回一个满足顾客要求的披萨,并计算价格:

package main

import "fmt"

type IPizza interface {
    getPrice() int
}

// 基类: 素食披萨
type Vegetable struct {
}

func (v Vegetable) getPrice() int {
    return 10
}

// 装饰器1: 奶酪装饰器
type Cheese struct {
    pizza IPizza
}

func (c Cheese) getPrice() int {
    return c.pizza.getPrice() + 3
}

// 装饰器2:
type Tomato struct {
    pizza IPizza
}

func (c Tomato) getPrice() int {
    return c.pizza.getPrice() + 4
}

func main() {

    vegetablePizza := Vegetable{}

    cheeseVegePizza := Cheese{vegetablePizza}

    tomatoCheeseVegePizza := Tomato{cheeseVegePizza}

    fmt.Printf("加了番茄和奶酪的披萨最终价格:%d\n", tomatoCheeseVegePizza.getPrice())

}


// output
// 加了番茄和奶酪的披萨最终价格:17

2.适配器模式

适配器模式可以通过一个中间层使不兼容的两个对象互相合作,适配器接收对象对其调用,并将此调用装换为对另一个对象的调用。适配就好比现实世界中的扩展坞将A和B
两个接口之间做了一层装换。

下面代码描述了如何通过适配器让只支持usb的windows电脑,也能使用雷电接口:

package main

import "fmt"

type Computer interface {
    InsertIntoLightningPort()
}

type Client struct {
}

// 给电脑插入雷电接口
func (t Client) InsertLightIntoComputer(c Computer) {
    c.InsertIntoLightningPort()
}

type Mac struct {
}

// mac电脑使用雷电接口
func (m Mac) InsertIntoLightningPort() {
    fmt.Println("给mac电脑插入雷电接口")
}

type Windows struct {
}

// windows电脑使用usb接口
func (m Windows) InsertIntoUsbPort() {
    fmt.Println("给windows电脑插入usb接口")
}

type WindowsAdapter struct {
    windows Windows
}

// 适配器 将雷电接口转为usb接口
func (w WindowsAdapter) InsertIntoLightningPort() {
    fmt.Println("转换雷电接口为usb接口")
    w.windows.InsertIntoUsbPort()
}

func main() {
    mac := Mac{}
    client := Client{}

    client.InsertLightIntoComputer(mac)

    windows := Windows{}

    adapter := WindowsAdapter{windows: windows}

    client.InsertLightIntoComputer(adapter)
}


// output
// 给mac电脑插入雷电接口
// 转换雷电接口为usb接口
// 给windows电脑插入usb接口

3.代理模式

代理模式可以替代原对象,处理对原对象的调用,通常会在对原对象的调用前后做一些同一的处理,例如nginx代理web应用处理请求,在流量真正到达
web应用程序前做请求的负载均衡,之后决定将请求转发给哪台服务器。

下面代码实现了nginx代理web应用程序做接口限流:

package main

import "fmt"

// web服务应该具有处理请求的能力
type Server interface {
    handleRequest(url, method string) (int, string)
}

// web应用程序
type Application struct {
}

func (a Application) handleRequest(url, method string) (int, string) {
    if url == "/app/status" && method == "GET" {
        return 200, "Ok"
    }

    if url == "/create/user" && method == "POST" {
        return 200, "User Created Success!"
    }
    return 404, "404 Not Found"
}

// nginx 代理web应用处理请求,做api接口请求限流
type NginxServer struct {
    application  Application
    MaxReqNum    int            // 最大请求数
    LimitRateMap map[string]int // 缓存每个接口的请求数
}

func NewNginxServer(app Application, max int) *NginxServer {
    return &NginxServer{
        application:  app,
        MaxReqNum:    max,
        LimitRateMap: make(map[string]int),
    }
}

// 代理web应用请求
func (n NginxServer) handleRequest(url, method string) (int, string) {
    if !n.checkReqRate(url) {
        return 403, "Not Allowed"
    }

    // 接口限流后转发请求到真实web应用
    return n.application.handleRequest(url, method)
}

// 接口限流和缓存
func (n *NginxServer) checkReqRate(url string) bool {
    reqNum := n.LimitRateMap[url]

    if reqNum >= n.MaxReqNum {
        return false
    }
    n.LimitRateMap[url]++

    return true
}

func main() {

    nginx := NewNginxServer(Application{}, 2)
    respCode, respBody := nginx.handleRequest("/app/status", "GET")
    fmt.Printf("URL:%s \n返回状态码:%d,响应内容:%s \n\n", "/app/status", respCode, respBody)

    respCode, respBody = nginx.handleRequest("/app/status", "GET")
    fmt.Printf("URL:%s \n返回状态码:%d,响应内容:%s \n\n", "/app/status", respCode, respBody)

    // 超过了最大限流数 返回403
    respCode, respBody = nginx.handleRequest("/app/status", "GET")
    fmt.Printf("URL:%s \n返回状态码:%d,响应内容:%s \n\n", "/app/status", respCode, respBody)

    respCode, respBody = nginx.handleRequest("/create/user", "POST")
    fmt.Printf("URL:%s \n返回状态码:%d,响应内容:%s \n\n", "/create/user", respCode, respBody)

}

/* output
URL:/app/status
返回状态码:200,响应内容:Ok

URL:/app/status
返回状态码:200,响应内容:Ok

URL:/app/status
返回状态码:403,响应内容:Not Allowed

URL:/create/user
返回状态码:200,响应内容:User Created Success!

*/

4.总结

下面是分别是这3种设计模式的常见应用场景:

设计模式 常见应用场景
装饰器模式 不修改原有对象结构,运行时为对象新增额外功能
适配器模式 想使用某个类,但这个类和其他代码不兼容时,创建一个中间层类
代理模式 延迟初始化真实对象,先使用虚拟代理,请求代理(记录日志,请求缓存,请求限流,代理远程服务)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,919评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,567评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,316评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,294评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,318评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,245评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,120评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,964评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,376评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,592评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,764评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,460评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,070评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,697评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,846评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,819评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,665评论 2 354

推荐阅读更多精彩内容