代理模式

目录

  • 代理模式
      • 静态代理:
      • 动态代理:
      • 代理模式的优点:
    • 代码实现
      • 静态代理
        • 代码
        • 单元测试
      • Go Generate 实现 “动态代理”
        • 需求
        • 代码
        • 单元测试
      • 仿照java的jdk动态代理实现go语言动态代理
        • 测试

静态代理:

  1. 代理类实现和目标类相同的接口,每个类都单独编辑一个代理类。
  2. 我们需要在代理类中,将目标类中的所有方法都要重新实现,并且为每个方法都附加相似的代码逻辑。
  3. 如果要添加方法增强的类不止一个,我们需要对每个类都创建一个代理类。

动态代理:

  1. 不需要为每个目标类编辑代理类。
  2. 在程序运行时,系统会动态地创建代理类,然后用代理类替换掉原始类。
  3. 一般采用反射实现。

代理模式的优点:

  1. 代理模式能将代理对象与真实被调用目标对象分离。
  2. 在一定程度上降低了系统的耦合性,拓展性好。
  3. 可以起到保护目标对象的作用。
  4. 可以增强目标对象的功能。

代码实现

接下来会通过 golang 实现静态代理,有 Golang 和 java 的差异性,我们无法比较方便的利用反射实现动态代理,但是我们可以利用go generate实现类似的效果,并且这样实现有两个比较大的好处,一个是有静态代码检查,我们在编译期间就可以及早发现问题,第二个是性能会更好。

静态代理

代码

package proxyimport ("log""time"
)// IUser IUser
type IUser interface {Login(username, password string) error
}// User 用户
type User struct {}// Login 用户登录
func (u *User) Login(username, password string) error {// 不实现细节return nil
}// UserProxy 代理类
type UserProxy struct {user *User
}// NewUserProxy NewUserProxy
func NewUserProxy(user *User) *UserProxy {return &UserProxy{user: user,}
}// Login 登录,和 user 实现相同的接口
func (p *UserProxy) Login(username, password string) error {// before 这里可能会有一些统计的逻辑start := time.Now()// 这里是原有的业务逻辑if err := p.user.Login(username, password); err != nil {return err}// after 这里可能也有一些监控统计的逻辑log.Printf("user login cost time: %s", time.Now().Sub(start))return nil
}

单元测试

package proxyimport ("testing""github.com/stretchr/testify/require"
)func TestUserProxy_Login(t *testing.T) {proxy := NewUserProxy(&User{})err := proxy.Login("test", "password")require.Nil(t, err)
}

Go Generate 实现 “动态代理”

注意: 在真实的项目中并不推荐这么做,因为有点得不偿失,本文只是在探讨一种可能性,并且可以复习一下 go 语法树先关的知识点
接下来我们先来看看需求。

需求

动态代理相比静态代理主要就是为了解决生产力,将我们从繁杂的重复劳动中解放出来,正好,在 Go 中 Generate 也是干这个活的
如下面的代码所示,我们的 generate 会读取 struct 上的注释,如果出现 @proxy 接口名 的注释,我们就会为这个 struct 生成一个 proxy 类,同时实现相同的接口,这个接口就是在注释中指定的接口

// User 用户
// @proxy IUser
type User struct {}

代码

接来下我们会简单的实现这个需求,由于篇幅和时间的关系,我们会略过一些检查之类的代码,例如 User 是否真正实现了 IUser 这种情况。
代码有点长,主要思路:

  • 读取文件, 获取文件的 ast 语法树
  • 通过 NewCommentMap 构建 node 和 comment 的关系
  • 通过 comment 是否包含 @proxy 接口名 的接口,判断该节点是否需要生成代理类
  • 通过 Lookup 方法找到接口
  • 循环获取接口的每个方法的,方法名、参数、返回值信息
  • 将方法信息,包名、需要代理类名传递给构建好的模板文件,生成代理类
  • 最后用 format 包的方法格式化源代码
package proxyimport ("bytes""fmt""go/ast""go/format""go/parser""go/token""strings""text/template"
)func generate(file string) (string, error) {fset := token.NewFileSet() // positions are relative to fsetf, err := parser.ParseFile(fset, file, nil, parser.ParseComments)if err != nil {return "", err}// 获取代理需要的数据data := proxyData{Package: f.Name.Name,}// 构建注释和 node 的关系cmap := ast.NewCommentMap(fset, f, f.Comments)for node, group := range cmap {// 从注释 @proxy 接口名,获取接口名称name := getProxyInterfaceName(group)if name == "" {continue}// 获取代理的类名data.ProxyStructName = node.(*ast.GenDecl).Specs[0].(*ast.TypeSpec).Name.Name// 从文件中查找接口obj := f.Scope.Lookup(name)// 类型转换,注意: 这里没有对断言进行判断,可能会导致 panict := obj.Decl.(*ast.TypeSpec).Type.(*ast.InterfaceType)for _, field := range t.Methods.List {fc := field.Type.(*ast.FuncType)// 代理的方法method := &proxyMethod{Name: field.Names[0].Name,}// 获取方法的参数和返回值method.Params, method.ParamNames = getParamsOrResults(fc.Params)method.Results, method.ResultNames = getParamsOrResults(fc.Results)data.Methods = append(data.Methods, method)}}// 生成文件tpl, err := template.New("").Parse(proxyTpl)if err != nil {return "", err}buf := &bytes.Buffer{}if err := tpl.Execute(buf, data); err != nil {return "", err}// 使用 go fmt 对生成的代码进行格式化src, err := format.Source(buf.Bytes())if err != nil {return "", err}return string(src), nil
}// getParamsOrResults 获取参数或者是返回值
// 返回带类型的参数,以及不带类型的参数,以逗号间隔
func getParamsOrResults(fields *ast.FieldList) (string, string) {var (params     []stringparamNames []string)for i, param := range fields.List {// 循环获取所有的参数名var names []stringfor _, name := range param.Names {names = append(names, name.Name)}if len(names) == 0 {names = append(names, fmt.Sprintf("r%d", i))}paramNames = append(paramNames, names...)// 参数名加参数类型组成完整的参数param := fmt.Sprintf("%s %s",strings.Join(names, ","),param.Type.(*ast.Ident).Name,)params = append(params, strings.TrimSpace(param))}return strings.Join(params, ","), strings.Join(paramNames, ",")
}func getProxyInterfaceName(groups []*ast.CommentGroup) string {for _, commentGroup := range groups {for _, comment := range commentGroup.List {if strings.Contains(comment.Text, "@proxy") {interfaceName := strings.TrimLeft(comment.Text, "// @proxy ")return strings.TrimSpace(interfaceName)}}}return ""
}// 生成代理类的文件模板
const proxyTpl = `
package {{.Package}}type {{ .ProxyStructName }}Proxy struct {child *{{ .ProxyStructName }}
}func New{{ .ProxyStructName }}Proxy(child *{{ .ProxyStructName }}) *{{ .ProxyStructName }}Proxy {return &{{ .ProxyStructName }}Proxy{child: child}
}{{ range .Methods }}
func (p *{{$.ProxyStructName}}Proxy) {{ .Name }} ({{ .Params }}) ({{ .Results }}) {// before 这里可能会有一些统计的逻辑start := time.Now(){{ .ResultNames }} = p.child.{{ .Name }}({{ .ParamNames }})// after 这里可能也有一些监控统计的逻辑log.Printf("user login cost time: %s", time.Now().Sub(start))return {{ .ResultNames }}
}
{{ end }}
`type proxyData struct {// 包名Package string// 需要代理的类名ProxyStructName string// 需要代理的方法Methods []*proxyMethod
}// proxyMethod 代理的方法
type proxyMethod struct {// 方法名Name string// 参数,含参数类型Params string// 参数名ParamNames string// 返回值Results string// 返回值名ResultNames string
}

单元测试

package proxyimport ("testing""github.com/stretchr/testify/assert""github.com/stretchr/testify/require"
)func Test_generate(t *testing.T) {want := `package proxytype UserProxy struct {child *User
}func NewUserProxy(child *User) *UserProxy {return &UserProxy{child: child}
}func (p *UserProxy) Login(username, password string) (r0 error) {// before 这里可能会有一些统计的逻辑start := time.Now()r0 = p.child.Login(username, password)// after 这里可能也有一些监控统计的逻辑log.Printf("user login cost time: %s", time.Now().Sub(start))return r0
}
`got, err := generate("./static_proxy.go")require.Nil(t, err)assert.Equal(t, want, got)
}

仿照java的jdk动态代理实现go语言动态代理

package proimport ("errors""fmt""reflect"
)//提供动态调用方法接口
type InvocationHandler interface {Invoke(proxy *Proxy, method *Method, args []interface{}) ([]interface{}, error)
}//代理,用来总管代理类的生成
type Proxy struct {target  interface{}        //目标类,后面的类型和java的Object一样methods map[string]*Method //map用来装载待增强的不同的方法handle  InvocationHandler  //用来暴露统一invoke接口,类似多态
}//创建新的代理
func NewProxy(target interface{}, h InvocationHandler) *Proxy {typ := reflect.TypeOf(target)          //用来显示目标类动态的真实类型value := reflect.ValueOf(target)       //获取目标类的值methods := make(map[string]*Method, 0) //初始化目标类的方法map//将目标类的方法逐个装载for i := 0; i < value.NumMethod(); i++ {method := value.Method(i)methods[typ.Method(i).Name] = &Method{value: method}}return &Proxy{target: target, methods: methods, handle: h}
}//代理调用代理方法
func (p *Proxy) InvokeMethod(name string, args ...interface{}) ([]interface{}, error) {return p.handle.Invoke(p, p.methods[name], args)
}//用来承载目标类的方法定位和调用
type Method struct {value reflect.Value //用来装载方法实例
}//这里相当于调用原方法,在该方法外可以做方法增强,需要调用者自己实现!!!
func (m *Method) Invoke(args ...interface{}) (res []interface{}, err error) {defer func() {//用来捕捉异常if p := recover(); p != nil {err = errors.New(fmt.Sprintf("%s", p))}}()//处理参数params := make([]reflect.Value, 0)if args != nil {for i := 0; i < len(args); i++ {params = append(params, reflect.ValueOf(args[i]))}}//调用方法call := m.value.Call(params)//接收返回值res = make([]interface{}, 0)if call != nil && len(call) > 0 {for i := 0; i < len(call); i++ {res = append(res, call[i].Interface())}}return
}

测试

package proimport ("fmt""testing""time"
)func TestName(t *testing.T) {//这里对活动时长做统计people := &People{}   //创建目标类h := new(PeopleProxy) //创建接口实现类proxy := NewProxy(people, h)//调用方法ret, err := proxy.InvokeMethod("Work", "敲代码", "学习")if err != nil {fmt.Println(err)}fmt.Println(ret)
}//目标类
type People struct {}func (p *People) Work(content string, next string) string {fmt.Println("活动内容是:" + content + ",接下来需要做:" + next)return "all right"
}//用户需要自己实现的增强内容,需要实现InvocationHandler接口
type PeopleProxy struct {}//在这里做方法增强
func (p *PeopleProxy) Invoke(proxy *Proxy, method *Method, args []interface{}) ([]interface{}, error) {start := time.Now()defer fmt.Printf("耗时:%v\n", time.Since(start))fmt.Println("before method")invoke, err := method.Invoke(args...)fmt.Println("after method")return invoke, err
}

参考我之前写的动态代理

https://blog.csdn.net/qq_53267860/article/details/126164229?spm=1001.2014.3001.5501

golang设计模式——代理模式相关推荐

  1. Python设计模式-代理模式

    Python设计模式-代理模式 基于Python3.5.2,代码如下 #coding:utf-8info_struct = dict() info_struct["addr"] = ...

  2. Java设计模式(代理模式-模板方法模式-命令模式)

    Java设计模式Ⅴ 1.代理模式 1.1 代理模式概述 1.2 静态代理 1.2.1 静态代理概述 1.2.2 代码理解 1.3 动态代理之JDK代理 1.3.1 动态代理之JDK代理概述 1.3.2 ...

  3. 设计模式——代理模式

    设计模式--代理模式 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能 ...

  4. 23种设计模式----------代理模式(一)

    代理模式也叫委托模式. 代理模式定义:对其他对象提供一种代理从而控制对这个对象的访问.就是,代理类 代理 被代理类,来执行被代理类里的方法. 一般情况下,代理模式化有三个角色. 1,抽象的主题类(或者 ...

  5. java设计模式代理模式_Java中的代理设计模式

    java设计模式代理模式 代理对象或代理对象为另一个对象提供占位符,以控制对该对象的访问. 代理充当原始对象的轻量级版本或简化版本. 它支持与原始对象相同的操作,但可以将那些请求委托给原始对象以实现它 ...

  6. 第四章 Caché 设计模式 代理模式

    文章目录 第四章 Caché 设计模式 代理模式 定义 类型 使用场景 优点 缺点 结构图 完整示例 抽象主题类 真实主题类 代理类 对象类 调用 思考 第四章 Caché 设计模式 代理模式 定义 ...

  7. Android常见设计模式——代理模式(Proxy Pattern)(二)

    文章目录 1. 前言 2. 远程代理(Remote Proxy) 3. 后记 1. 前言 在上篇Android常见设计模式--代理模式(Proxy Pattern)中基本上知道了什么是代理模式,以及对 ...

  8. sheng的学习笔记-设计模式-代理模式

    原理图: 代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问.这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介. 至少在以下集中情况下可以用 ...

  9. 设计模式-代理模式(Proxy Pattern)

    设计模式-代理模式(Proxy Pattern) 文章目录 设计模式-代理模式(Proxy Pattern) 一.定义 二.概念解释 三.场景 四.实现 1.类图 2.代码实现 五.小结 六.动态代理 ...

最新文章

  1. 遗传算法求解几何问题
  2. js生日计算年龄_JS根据生日算年龄的方法
  3. Docker 更换下载镜像源
  4. 如何在mapinfo中查看link ID
  5. win7计算机用户名在哪找,win7 c盘里找不到users,用户里也没有C:#92;User...-win7电脑c盘USERS文件夹在哪...
  6. 查看-增强会话_会话式人工智能-关键技术和挑战-第2部分
  7. 三年级优秀书籍推荐_三年级课外推荐阅读书目
  8. ZooKeeper动态重新配置
  9. 20161212 输出1到n之间所有的奇(单)数(n30000) 。
  10. 层级菜单构造--groupingby
  11. 7-59 二元一次方程的解
  12. 面对互联网寒冬裁员潮,HR都在干什么?
  13. 使用 Fabric CA 生成 MSP
  14. 广州高清卫星地图 用百度卫星地图server下载 含标签、道路数据叠加 可商用
  15. 【量亿数据-level2】唐奇安趋势跟踪系统
  16. 苹果usb设置在哪里_努比亚红魔3红魔3S打开开发者选项和USB调试功能
  17. 2022年湖南省临床执业医师考试第三单元综合(二)
  18. 一文搞懂Kiss、Yagni原则
  19. SetFocus()函数
  20. 天时地利人和—一个传奇操作系统的诞生记

热门文章

  1. 大数据开始盛行,感觉到关系型数据库的无力
  2. python实现UDP打洞
  3. 教堂 寺庙cc0高清摄影图片素材推荐 精品 小众
  4. 网站百度权重竟然升了,经验分享
  5. c语言中的beep函数,c++ Beep函数
  6. JavaScript实现下拉菜单
  7. 单一世界架构初探之动态分区
  8. 小程序Echarts图表组件使用
  9. Excel数值函数(5):COUNTIF,统计符合指定条件的单元格数量
  10. 事件冒泡、事件捕获、http与https