结构型模式解决什么问题
结构模式关注类和对象的组合,解决如何将类和对象组装成较大结构的同时,保持结构的灵活和可复用性。
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种设计模式的常见应用场景:
设计模式 | 常见应用场景 |
---|---|
装饰器模式 | 不修改原有对象结构,运行时为对象新增额外功能 |
适配器模式 | 想使用某个类,但这个类和其他代码不兼容时,创建一个中间层类 |
代理模式 | 延迟初始化真实对象,先使用虚拟代理,请求代理(记录日志,请求缓存,请求限流,代理远程服务) |