实践GoF的设计模式:代理模式

实践,gof,设计模式,代理,模式 · 浏览次数 : 71

小编点评

**标题:Go实现实践GoF的23种设计模式:从SOLID原则到代理模式** **引言:** Go实现是一种强大的框架,可以帮助开发者快速构建高效的应用程序。然而,在一些情况下,我们可能需要使用其他模式来提升应用程序的性能。本文将介绍23种设计模式,帮助开发者在Go框架中构建更高效的应用程序。 **SOLID原则:** SOLID原则是设计模式的核心原则,它帮助开发者在构建应用程序时保持代码的可维护性。 * **单一职责原则:**一个函数只能处理一个特定的任务。 * **依赖倒入原则:**依赖对象应该在应用中进行注入。 * **抽象原则:**抽象器应该提供一个抽象的接口。 **代理模式:** 代理模式是一种将应用程序代理到另一个应用程序的技术。 * **远程代理:**应用程序向另一个应用程序发送请求。 * **缓存代理:**应用程序向另一个应用程序发送缓存请求。 **装饰模式:** 装饰模式是一种将应用程序与另一个应用程序之间的装饰器。 * **日志装饰器:**将日志记录到另一个应用程序中。 * **安全装饰器:**将安全机制添加到应用程序中。 **元闰子:** 元闰子是一种可以将多个设计模式组合在一起的模式。 * **代理元闰子:**代理模式与元闰子模式结合在一起。 * **装饰元闰子:**装饰模式与元闰子模式结合在一起。 **设计模式:** 设计模式是帮助我们设计和构建高效应用程序的技术。 * **结构模式:**结构模式帮助我们将应用程序结构化。 * **代理模式:**代理模式帮助我们将应用程序代理到其他应用程序。 * **装饰模式:**装饰模式帮助我们将应用程序与其他应用程序之间的装饰器。 **实现:** 每个设计模式都有相应的实现,帮助开发者在Go框架中构建更高效的应用程序。 **结论:** Go实现是一个强大的框架,可以帮助开发者快速构建高效的应用程序。使用设计模式可以帮助我们设计更高效的应用程序,提升应用程序的性能。

正文

摘要: 代理模式为一个对象提供一种代理以控制对该对象的访问。

本文分享自华为云社区《【Go实现】实践GoF的23种设计模式:代理模式》,作者:元闰子 。

简介

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

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

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

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

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

UML 结构

场景上下文

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

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

代码实现

// demo/db/cache.go
package db
// 关键点1: 定义代理对象,实现被代理对象的接口
type CacheProxy struct {
 // 关键点2: 组合被代理对象,这里应该是抽象接口,提升可扩展性
 db    Db
    cache sync.Map // key为tableName,value为sync.Map[key: primaryId, value: interface{}]
    hit   int
 miss  int
}
// 关键点3: 在具体接口实现上,嵌入代理本身的逻辑
func (c *CacheProxy) Query(tableName string, primaryKey interface{}, result interface{}) error {
    cache, ok := c.cache.Load(tableName)
 if ok {
 if record, ok := cache.(*sync.Map).Load(primaryKey); ok {
 c.hit++
            result = record
 return nil
 }
 }
 c.miss++
 if err := c.db.Query(tableName, primaryKey, result); err != nil {
 return err
 }
 cache.(*sync.Map).Store(primaryKey, result)
 return nil
}
func (c *CacheProxy) Insert(tableName string, primaryKey interface{}, record interface{}) error {
 if err := c.db.Insert(tableName, primaryKey, record); err != nil {
 return err
 }
    cache, ok := c.cache.Load(tableName)
 if !ok {
 return nil
 }
 cache.(*sync.Map).Store(primaryKey, record)
 return nil
}
...
// 关键点4: 代理也可以有自己特有方法,提供一些辅助的功能
func (c *CacheProxy) Hit() int {
 return c.hit
}
func (c *CacheProxy) Miss() int {
 return c.miss
}
...

客户端这样使用:

// 客户端只看到抽象的Db接口
func client(db Db) {
 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)
}
func main() {
 // 关键点5: 在初始化阶段,完成缓存的实例化,并依赖注入到客户端
 cache := NewCacheProxy(&memoryDb{tables: sync.Map{}})
 client(cache)
}

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

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

  1. 定义代理对象,实现被代理对象的接口。本例子中,前者是 CacheProxy 对象,后者是 Db 接口。
  2. 代理对象组合被代理对象,这里组合的应该是抽象接口,让代理的可扩展性更高些。本例子中,CacheProxy 对象组合了 Db 接口。
  3. 代理对象在具体接口实现上,嵌入代理本身的逻辑。本例子中,CacheProxy 在 Query、Insert 等方法中,加入了缓存 sync.Map 的读写逻辑。
  4. 代理对象也可以有自己特有方法,提供一些辅助的功能。本例子中,CacheProxy 新增了Hit、Miss 等方法用于统计缓存的命中率。
  5. 最后,在初始化阶段,完成代理的实例化,并依赖注入到客户端。这要求,客户端依赖抽象接口,而不是具体实现,否则代理就不透明了。

扩展

Go 标准库中的反向代理

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

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

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

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

// net/http/httputil/reverseproxy.go
package httputil
type ReverseProxy struct {
 // 修改前端请求,然后通过Transport将修改后的请求转发给后端
    Director func(*http.Request)
 // 可理解为Subject,通过Transport来调用被代理对象的ServeHTTP方法处理请求
    Transport http.RoundTripper
 // 修改后端响应,并将修改后的响应返回给前端
 ModifyResponse func(*http.Response) error
 // 错误处理
 ErrorHandler func(http.ResponseWriter, *http.Request, error)
 ...
}
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 // 初始化transport
 transport := p.Transport
 if transport == 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:

func proxy(c *gin.Context) {
    remote, err := url.Parse("https://yrunz.com")
 if err != 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)
}
func main() {
 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画出手绘风格的配图 中找到文章的绘图方法。

参考

[1] 【Go实现】实践GoF的23种设计模式:SOLID原则, 元闰子

[2] 【Go实现】实践GoF的23种设计模式:装饰模式, 元闰子

[3] Design Patterns, Chapter 4. Structural Patterns, GoF

[4] 代理模式, refactoringguru.cn

[5] 什么是反向代理?, cloudflare

 

点击关注,第一时间了解华为云新鲜技术~

与实践GoF的设计模式:代理模式相似的内容:

实践GoF的设计模式:代理模式

摘要: 代理模式为一个对象提供一种代理以控制对该对象的访问。 本文分享自华为云社区《【Go实现】实践GoF的23种设计模式:代理模式》,作者:元闰子 。 简介 GoF 对代理模式(Proxy Pattern)的定义如下: Provide a surrogate or placeholder for

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

摘要:命令模式可将请求转换为一个包含与请求相关的所有信息的对象, 它能将请求参数化、延迟执行、实现 Undo / Redo 操作等。 本文分享自华为云社区《【Go实现】实践GoF的23种设计模式:命令模式》,作者:元闰子。 简介 现在的软件系统往往是分层设计。在业务层执行一次请求时,我们很清楚请求的

实践探讨Python如何进行异常处理与日志记录

本文分享自华为云社区《Python异常处理与日志记录构建稳健可靠的应用》,作者:柠檬味拥抱。 异常处理和日志记录是编写可靠且易于维护的软件应用程序中至关重要的组成部分。Python提供了强大的异常处理机制和灵活的日志记录功能,使开发人员能够更轻松地管理代码中的错误和跟踪应用程序的执行过程。在本文中,

基于Traefik如何实现向后转发自动去掉前缀?

实践中, 往往会有这样的需求, 用户输入的url是ewhisper.cn/alert-manager/#/alerts, 但是转发到后端要变成/#/alerts, 如何基于 Traefik on K8S 实现?

实践丨GaussDB(DWS)资源管理排队原理与问题定位

摘要:GaussDB(DWS)提供了资源管理功能,用户可以根据自身业务情况对资源进行划分,将资源按需划分成不同的资源池,不同资源池之间资源互相隔离。 本文分享自华为云社区《GaussDB(DWS)资源管理排队原理与问题定位》,作者: 门前一棵葡萄树 。 一、内存管控原理 GaussDB(DWS)提供

实践篇(三):如何有效评审软件架构图?

设计意图的传达是架构可视化关注的重要维度,在技术方案评审过程中不可避免的会出现各种各样的架构图或设计图,这些图形化表述在设计意图传达效果层面表现不一,本文从图形化的视角为软件架构图的评审关注点提供了参考。

实践指南-前端性能提升 270%

当我们疲于开发一个接一个的需求时,很容易忘记去关注网站的性能,到了某一个节点,猛地发现,随着越来越多代码的堆积,网站变得越来越慢。本文就是从这样的一个背景出发,着手优化网站的前端性能,并总结出一套开发习惯,让我们在日常开发时,也保持高性能,而不是又一次回过头来优化性能。

【实践篇】DDD脚手架及编码规范

我们团队一直在持续推进业务系统的体系化治理工作,在这个过程中我们沉淀了自己的DDD脚手架项目。本文主要是梳理和总结了DDD脚手架使用中的编码规范以及遇到的问题。

实践案例丨CenterNet-Hourglass论文复现

摘要:本案例是CenterNet-Hourglass论文复现的体验案例,此模型是对Objects as Points 中提出的CenterNet进行结果复现。 本文分享自华为云社区《CenterNet-Hourglass (物体检测/Pytorch)》,作者:HWCloudAI。 目标检测常采用An

实践Pytorch中的模型剪枝方法

摘要:所谓模型剪枝,其实是一种从神经网络中移除"不必要"权重或偏差的模型压缩技术。 本文分享自华为云社区《模型压缩-pytorch 中的模型剪枝方法实践》,作者:嵌入式视觉。 一,剪枝分类 所谓模型剪枝,其实是一种从神经网络中移除"不必要"权重或偏差(weigths/bias)的模型压缩技术。关于什