0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

GoF设计模式之代理模式

元闰子的邀请 来源:元闰子的邀请 作者:元闰子的邀请 2022-10-17 09:45 次阅读

上一篇:【Go实现】实践GoF的23种设计模式:访问者模式

简单的分布式应用系统(示例代码工程):https://github.com/ruanrunxue/Practice-Design-Pattern--Go-Implementation

简介

GoF 对代理模式(Proxy Pattern)的定义如下:

Provide a surrogate or placeholder for another object to control access to it.

也即,代理模式为一个对象提供一种代理以控制对该对象的访问

它是一个使用率非常高的设计模式,在现实生活中,也是很常见。比如,演唱会门票黄牛。假设你需要看一场演唱会,但官网上门票已经售罄,于是就当天到现场通过黄牛高价买了一张。在这个例子中,黄牛就相当于演唱会门票的代理,在正式渠道无法购买门票的情况下,你通过代理完成了该目标。

从演唱会门票的例子我们也能看出,使用代理模式的关键在于,当 Client 不方便直接访问一个对象时,提供一个代理对象控制该对象的访问。Client 实际上访问的是代理对象,代理对象会将 Client 的请求转给本体对象去处理。

UML 结构

5f53c40e-4d3e-11ed-a3b6-dac502259ad0.jpg

场景上下文

在简单的分布式应用系统(示例代码工程)中,db 模块用来存储服务注册和监控信息,它是一个 key-value 数据库。为了提升访问数据库的性能,我们决定为它新增一层缓存:

5f860194-4d3e-11ed-a3b6-dac502259ad0.jpg

另外,我们希望客户端在使用数据库时,并不感知缓存的存在,这些,代理模式可以做到。

代码实现

//demo/db/cache.go
packagedb

//关键点1:定义代理对象,实现被代理对象的接口
typeCacheProxystruct{
//关键点2:组合被代理对象,这里应该是抽象接口,提升可扩展性
dbDb
cachesync.Map//key为tableName,value为sync.Map[key:primaryId,value:interface{}]
hitint
missint
}

//关键点3:在具体接口实现上,嵌入代理本身的逻辑
func(c*CacheProxy)Query(tableNamestring,primaryKeyinterface{},resultinterface{})error{
cache,ok:=c.cache.Load(tableName)
ifok{
ifrecord,ok:=cache.(*sync.Map).Load(primaryKey);ok{
c.hit++
result=record
returnnil
}
}
c.miss++
iferr:=c.db.Query(tableName,primaryKey,result);err!=nil{
returnerr
}
cache.(*sync.Map).Store(primaryKey,result)
returnnil
}

func(c*CacheProxy)Insert(tableNamestring,primaryKeyinterface{},recordinterface{})error{
iferr:=c.db.Insert(tableName,primaryKey,record);err!=nil{
returnerr
}
cache,ok:=c.cache.Load(tableName)
if!ok{
returnnil
}
cache.(*sync.Map).Store(primaryKey,record)
returnnil
}

...

//关键点4:代理也可以有自己特有方法,提供一些辅助的功能
func(c*CacheProxy)Hit()int{
returnc.hit
}

func(c*CacheProxy)Miss()int{
returnc.miss
}

...

客户端这样使用:

//客户端只看到抽象的Db接口
funcclient(dbDb){
table:=NewTable("region").
WithType(reflect.TypeOf(new(testRegion))).
WithTableIteratorFactory(NewRandomTableIteratorFactory())
db.CreateTable(table)
table.Insert(1,&testRegion{Id:1,Name:"region"})

result:=new(testRegion)
db.Query("region",1,result)
}

funcmain(){
//关键点5:在初始化阶段,完成缓存的实例化,并依赖注入到客户端
cache:=NewCacheProxy(&memoryDb{tables:sync.Map{}})
client(cache)
}

本例子中,Subject 是Db接口,Proxy 是CacheProxy对象,SubjectImpl 是memoryDb对象:

5fa4c21e-4d3e-11ed-a3b6-dac502259ad0.jpg

总结实现代理模式的几个关键点:

定义代理对象,实现被代理对象的接口。本例子中,前者是CacheProxy对象,后者是Db接口。

代理对象组合被代理对象,这里组合的应该是抽象接口,让代理的可扩展性更高些。本例子中,CacheProxy对象组合了Db接口。

代理对象在具体接口实现上,嵌入代理本身的逻辑。本例子中,CacheProxy在Query、Insert等方法中,加入了缓存sync.Map的读写逻辑。

代理对象也可以有自己特有方法,提供一些辅助的功能。本例子中,CacheProxy新增了Hit、Miss等方法用于统计缓存的命中率。

最后,在初始化阶段,完成代理的实例化,并依赖注入到客户端。这要求,客户端依赖抽象接口,而不是具体实现,否则代理就不透明了。

扩展

Go 标准库中的反向代理

代理模式最典型的应用场景是远程代理,其中,反向代理又是最常用的一种。

以 Web 应用为例,反向代理位于 Web 服务器前面,将客户端(例如 Web 浏览器)请求转发后端的 Web 服务器。反向代理通常用于帮助提高安全性、性能和可靠性,比如负载均衡、SSL 安全链接。

5fcd9482-4d3e-11ed-a3b6-dac502259ad0.jpg

Go 标准库的 net 包也提供了反向代理,ReverseProxy,位于net/http/httputil/reverseproxy.go下,实现http.Handler接口。http.Handler提供了处理 Http 请求的能力,也即相当于 Http 服务器。那么,对应到 UML 结构图中,http.Handler就是 Subject,ReverseProxy就是 Proxy:

5ffc9af2-4d3e-11ed-a3b6-dac502259ad0.jpg

下面列出ReverseProxy的一些核心代码:

//net/http/httputil/reverseproxy.go
packagehttputil

typeReverseProxystruct{
//修改前端请求,然后通过Transport将修改后的请求转发给后端
Directorfunc(*http.Request)
//可理解为Subject,通过Transport来调用被代理对象的ServeHTTP方法处理请求
Transporthttp.RoundTripper
//修改后端响应,并将修改后的响应返回给前端
ModifyResponsefunc(*http.Response)error
//错误处理
ErrorHandlerfunc(http.ResponseWriter,*http.Request,error)
...
}

func(p*ReverseProxy)ServeHTTP(rwhttp.ResponseWriter,req*http.Request){
//初始化transport
transport:=p.Transport
iftransport==nil{
transport=http.DefaultTransport
}
...
//修改前端请求
p.Director(outreq)
...
//将请求转发给后端
res,err:=transport.RoundTrip(outreq)
...
//修改后端响应
if!p.modifyResponse(rw,res,outreq){
return
}
...
//给前端返回响应
err=p.copyResponse(rw,res.Body,p.flushInterval(res))
...
}

ReverseProxy就是典型的代理模式实现,其中,远程代理无法直接引用后端的对象引用,因此这里通过引入Transport来远程访问后端服务,可以将Transport理解为 Subject。

可以这么使用ReverseProxy:

funcproxy(c*gin.Context){
remote,err:=url.Parse("https://yrunz.com")
iferr!=nil{
panic(err)
}

proxy:=httputil.NewSingleHostReverseProxy(remote)
proxy.Director=func(req*http.Request){
req.Header=c.Request.Header
req.Host=remote.Host
req.URL.Scheme=remote.Scheme
req.URL.Host=remote.Host
req.URL.Path=c.Param("proxyPath")
}

proxy.ServeHTTP(c.Writer,c.Request)
}

funcmain(){
r:=gin.Default()
r.Any("/*proxyPath",proxy)
r.Run(":8080")
}

典型应用场景

远程代理(remote proxy),远程代理适用于提供服务的对象处在远程的机器上,通过普通的函数调用无法使用服务,需要经过远程代理来完成。因为并不能直接访问本体对象,所有远程代理对象通常不会直接持有本体对象的引用,而是持有远端机器的地址,通过网络协议去访问本体对象

虚拟代理(virtual proxy),在程序设计中常常会有一些重量级的服务对象,如果一直持有该对象实例会非常消耗系统资源,这时可以通过虚拟代理来对该对象进行延迟初始化。

保护代理(protection proxy),保护代理用于控制对本体对象的访问,常用于需要给 Client 的访问加上权限验证的场景。

缓存代理(cache proxy),缓存代理主要在 Client 与本体对象之间加上一层缓存,用于加速本体对象的访问,常见于连接数据库的场景。

智能引用(smart reference),智能引用为本体对象的访问提供了额外的动作,常见的实现为 C++ 中的智能指针,为对象的访问提供了计数功能,当访问对象的计数为 0 时销毁该对象。

优缺点

优点

可以在客户端不感知的情况下,控制访问对象,比如远程访问、增加缓存、安全等。

符合开闭原则,可以在不修改客户端和被代理对象的前提下,增加新的代理;也可以在不修改客户端和代理的前提下,更换被代理对象。

缺点

作为远程代理时,因为多了一次转发,会影响请求的时延。

与其他模式的关联

从结构上看,装饰模式和 代理模式 具有很高的相似性,但是两种所强调的点不一样。前者强调的是为本体对象添加新的功能,后者强调的是对本体对象的访问控制

文章配图

可以在用Keynote画出手绘风格的配图中找到文章的绘图方法。

审核编辑:汤梓红
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 代码
    +关注

    关注

    30

    文章

    4555

    浏览量

    66762
  • 设计模式
    +关注

    关注

    0

    文章

    53

    浏览量

    8597
  • Client
    +关注

    关注

    0

    文章

    10

    浏览量

    8713
  • 代理模式
    +关注

    关注

    0

    文章

    3

    浏览量

    1729

原文标题:【Go实现】实践GoF的23种设计模式:代理模式

文章出处:【微信号:yuanrunzi,微信公众号:元闰子的邀请】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    适配器模式代理模式的区别

      代理模式  组成:  抽象角色:通过接口或抽象类声明真实角色实现的业务方法。  代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的
    发表于 10-22 15:17

    关于国产MCU GOF32F103C8T6 软硬件通用

    +105℃的扩展温度范围,一系列的省电模式保证低功耗应用的要求。GOF32F103X8和GOF32F103XB标准型系列产品提供包括从36脚至100脚的4种不同封装形式:根据不同的封装形式,器件中的外设配置
    发表于 04-19 09:50

    23种基本的设计模式总结

    一样。​提到设计模式,不得不感谢GoF(***,四人组),他们1995年出版的《设计模式》一书,第一次将设计模式提升到理论高度,并将之规范化。书中一共总结了23种基本的设计
    发表于 03-01 06:08

    Command模式与动态语言

    Gof的设计模式中,有一个模式引起的争议比较大,有很多人甚至认为这个模式应该排除在OO模式之外,原因在于它不具有OO的特性
    发表于 06-22 10:20 828次阅读
    Command<b class='flag-5'>模式</b>与动态语言

    适配器模式、装饰器模式代理模式的区别

    适配器模式、装饰器模式代理模式都属于设计模式中的结构型模式,结构型设计
    发表于 10-18 15:53 1.6w次阅读
    适配器<b class='flag-5'>模式</b>、装饰器<b class='flag-5'>模式</b>、<b class='flag-5'>代理</b><b class='flag-5'>模式</b>的区别

    适配器模式代理模式的区别

    适配器模式:适配器模式有时候也称包装样式或者包装。将一个类的接口转接成用户所期待的。代理模式:为其他对象提供一种代理以控制对这个对象的访问。
    发表于 01-12 11:56 5158次阅读
    适配器<b class='flag-5'>模式</b>和<b class='flag-5'>代理</b><b class='flag-5'>模式</b>的区别

    GoF给装饰者模式的定义

    的源码,就会发现 middleware 功能的实现用的就是装饰者模式(Decorator Pattern)。
    的头像 发表于 06-29 10:22 585次阅读

    GoF设计模式之访问者模式

    访问者模式的目的是,解耦数据结构和算法,使得系统能够在不改变现有代码结构的基础上,为对象新增一种新的操作。
    的头像 发表于 10-08 11:05 499次阅读

    嵌入式软件的设计模式(上)

    一般常见的是四人帮模式GOF的23种设计模式,是偏向于可复用的面向对象的软件,并不能很完美的契合嵌入式软件,因为嵌入式C语言是结构化的语言,与硬件关联。虽然也可强制封装结构体实现类似效果(复杂的嵌入式应用软件也可使用,但对于通
    的头像 发表于 01-20 11:32 890次阅读
    嵌入式软件的设计<b class='flag-5'>模式</b>(上)

    实践GoF的23种设计模式:命令模式简介

    因此,我们需要对请求进行抽象,将上下文信息封装到请求对象里,这其实就是命令模式,而该请求对象就是 Command。
    的头像 发表于 01-13 16:36 519次阅读

    设计模式结构性:代理模式

    代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式
    的头像 发表于 06-09 15:27 592次阅读
    设计<b class='flag-5'>模式</b>结构性:<b class='flag-5'>代理</b><b class='flag-5'>模式</b>

    装饰器模式代理模式的区别

    什么是装饰器模式 装饰器模式(Decorator Pattern): 在不改变对象自身的基础上,在程序运行期间给对象动态的添加职责; 感觉和继承如出一辙,不改变父类,子类可拓展功能; 优点 装饰
    的头像 发表于 10-08 14:25 496次阅读
    装饰器<b class='flag-5'>模式</b>和<b class='flag-5'>代理</b><b class='flag-5'>模式</b>的区别

    设计模式代理模式的使用场景

    设计模式在我看来更像是一种设计思维或设计思想,它就像《孙子兵法》一样,为你的项目工程提供方向,让你的项目工程更加健壮、灵活,延续生命力。本文即将分享的是设计模式的其中一种:代理模式
    的头像 发表于 10-08 14:34 399次阅读
    设计<b class='flag-5'>模式</b>中<b class='flag-5'>代理</b><b class='flag-5'>模式</b>的使用场景

    实践GoF的23种设计模式:备忘录模式

    相对于代理模式、工厂模式等设计模式,备忘录模式(Memento)在我们日常开发中出镜率并不高,除了应用场景的限制之外,另一个原因,可能是备忘
    的头像 发表于 11-25 09:05 269次阅读
    实践<b class='flag-5'>GoF</b>的23种设计<b class='flag-5'>模式</b>:备忘录<b class='flag-5'>模式</b>

    实践GoF的23种设计模式:解释器模式

    解释器模式(Interpreter Pattern)应该是 GoF 的 23 种设计模式中使用频率最少的一种了,它的应用场景较为局限。
    的头像 发表于 04-01 11:01 166次阅读
    实践<b class='flag-5'>GoF</b>的23种设计<b class='flag-5'>模式</b>:解释器<b class='flag-5'>模式</b>