轻松理解 Golang 代理模式
意图
接管外界对对象的访问。
张三的老板突然在发工资的前一天跑路了,可怜张三一身房贷,被迫提起劳动仲裁,劳动局就会为其指派一位代理律师全权负责张三的仲裁事宜。那这里面就是使用了代理模式,因为在劳动仲裁这个活动中,代理律师会全权代理张三。
实战
我们来针对 Nginx 代理 Web 服务,以 Nginx 对请求限制次数的角度写一段代码(纯属虚构) 首先,我们需要一个 Server:
type server interface {
handleRequest(string, string) (int, string)
}
之后,基于server接口
,实现我们的 app
type Application struct {}
func (a *Application) handleRequest(url, method string) (int, string) {
if url == "/app/status" && method == "GET" {
return 200, "Ok"
}
return 404, "Not Ok"
}
此时有一个新需求:接口请求限速。这种非业务功能需求并不适合在 app对象 内实现。首先就是不符合 SOLID 设计原则,再就是将功能需求和非功能需求混杂在一个是一个坏味道。 所以,我们实现一个 app 的代理 Nginx:它继承 app 类,实现了 server 接口。
type nginx struct {
application *Application
maxAllowedRequest int
rateLimiter map[string]int
}
func newNginxServer() *nginx {
return &nginx{
application: &Application{},
maxAllowedRequest: 2,
rateLimiter: make(map[string]int),
}
}
func (n *nginx) handleRequest(url, method string) (int, string) {
allowed := n.checkRateLimiting(url)
if !allowed {
return 403, "Not Allowed"
}
return n.application.handleRequest(url, method)
}
func (n *nginx) checkRateLimiting(url string) bool {
n.rateLimiter[url]++
if n.rateLimiter[url] > n.maxAllowedRequest {
return false
}
return true
}
我们跑一个测试,看 Nginx 是否实现了功能:
func TestProxy(t *testing.T) {
nginxServer := newNginxServer()
appStatusURL := "/app/status"
httpCode, body := nginxServer.handleRequest(appStatusURL, "GET")
fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)
httpCode, body = nginxServer.handleRequest(appStatusURL, "GET")
fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)
httpCode, body = nginxServer.handleRequest(appStatusURL, "GET") // 触发限速
fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)
}
-------------------输出--------------------
=== RUN TestProxy
Url: /app/status
HttpCode: 200
Body: Ok
Url: /app/status
HttpCode: 200
Body: Ok
Url: /app/status
HttpCode: 403
Body: Not Allowed
优缺点
根据上面例子我们可以看到,代理模式的优点是:
- 单一职责,没有侵入原实现(对应例子就是限速功能没有侵入到 app类)。
至于缺点……在我看来只要合理使用,代理模式没有缺点。
应用场景
现在我们知道了如何实现代理模式,以及它的优缺点,
- 如果想对重复请求所需返回的相同结果进行缓存,可以使用代理模式。
- 业务系统的非功能需求的开发,比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类中统一处理。
- RPC 框架基于代理模式实现,RPC 将网络通信、数据编解码等细节隐藏起来。客户端在使用 RPC 服务的时候,就像使用本地函数一样,无需了解跟服务器交互的细节。
动态代理
基于目前的研究,Golang 貌似无法实现 Java 中大名鼎鼎的动态代理(如果哪位大佬有研究,欢迎在评论区指出)。
虽然实现不了,不过我们还是要知道动态代理是什么:所谓动态代理,就是我们不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。
对应的,上面的示例叫静态代理,因为在编译期已经确定了代理类。