【设计模式】5、proxy 代理模式

server/2024/12/23 5:31:22/

五、proxy 代理模式

proxy 模式
https://refactoringguru.cn/design-patterns/proxy

如果 client 需要操作一个 rawObject, 但希望 proxy 它时, 则可使用 proxy 模式.

可抽象 proxy interface, 使 rawObject 和 proxyObject 都实现该 proxy interface.

有如下场景:

  1. 延迟初始化: 如果 rawObject 是一个消耗大量资源的巨型对象, 我们只是偶尔使用它的话. 可以用 proxyObject 封装一层, 当真正调用时再调用 rawObject 去初始化
  2. 缓存结果, 记录日志, 访问控制等.

通常, proxyObject 会管理 rawObject 的整个生命周期.

例如, 用户直接访问腾讯视频太慢了, 我们可以写一个代理, 它缓存视频. 详见 051 示例

5.1 tencent_video_proxy

用户直接从腾讯视频下载视频需要花钱, 而且慢. 但如果盗版网站代理的话, 提供同样的服务, 而且免费, 快速.

示例: https://refactoringguru.cn/design-patterns/proxy

目录层级

05proxy/051tencent_video_proxy
├── imovie_website.go
├── imovie_website_test.go
├── readme.md
├── tencent_video.go
└── video_website.go

5.1.1 接口

package _51tencent_video_proxyimport "fmt"// IMovieWebsite 私人电影网站
type IMovieWebsite struct {// 原始内容提供商, 是代理的对象: 如腾讯视频rawVideoWebsite VideoWebsite// 缓存的 videoscachedVideos map[int]string
}func NewIMovieWebsite(rawVideoWebsite VideoWebsite) VideoWebsite {return &IMovieWebsite{rawVideoWebsite: rawVideoWebsite,cachedVideos:    make(map[int]string),}
}// 私有方法, 拉取全部原始视频
func (im *IMovieWebsite) fetchRawVideos() {fmt.Println("[拉取原始视频列表] 开始")defer fmt.Println("[拉取原始视频列表] 结束")im.cachedVideos = im.rawVideoWebsite.listVideos()
}// 私有方法, 尝试拉取某原始视频
func (im *IMovieWebsite) fetchRawVideoIfNotExist(id int) {// fmt.Printf("[尝试拉取某原始视频%v] 开始\n", id)// defer fmt.Printf("[尝试拉取某原始视频%v] 结束\n", id)// 尝试从缓存中寻找_, ok := im.cachedVideos[id]if !ok {// 如果不存在则拉取im.fetchRawVideos()}
}func (im *IMovieWebsite) listVideos() map[int]string {// 如果无缓存, 则重新拉取if len(im.cachedVideos) == 0 {im.fetchRawVideos()}// 返回缓存的内容return im.cachedVideos
}func (im *IMovieWebsite) startVideo(id int) {im.fetchRawVideoIfNotExist(id)// 从缓存中寻找name, ok := im.cachedVideos[id]if !ok {fmt.Printf("视频%v不存在\n", id)return}fmt.Printf("开始播放id为%v的视频%v\n", id, name)
}func (im *IMovieWebsite) stopVideo(id int) {im.fetchRawVideoIfNotExist(id)// 从缓存中寻找name, ok := im.cachedVideos[id]if !ok {fmt.Printf("视频%v不存在\n", id)return}fmt.Printf("暂停播放id为%v的视频%v\n", id, name)
}func (im *IMovieWebsite) seekVideo(id int, pos float64) {im.fetchRawVideoIfNotExist(id)// 从缓存中寻找name, ok := im.cachedVideos[id]if !ok {fmt.Printf("视频%v不存在\n", id)return}fmt.Printf("跳转id为%v的视频%v, 进度到%v\n", id, name, pos)
}

5.1.2 原始:腾讯视频实现

package _51tencent_video_proxyimport "fmt"// TencentVideoWebsite 腾讯视频网站
type TencentVideoWebsite struct {// 视频内容, k: 视频id, v: 视频namevideos map[int]string
}func NewTencentVideoWebsite() VideoWebsite {return &TencentVideoWebsite{videos: map[int]string{1: "热辣滚烫", 2: "飞驰人生"}}
}func (t *TencentVideoWebsite) listVideos() map[int]string {return t.videos
}func (t *TencentVideoWebsite) startVideo(id int) {name, ok := t.videos[id]if !ok {fmt.Printf("视频%v不存在\n", id)return}fmt.Printf("开始播放id为%v的视频%v\n", id, name)
}func (t *TencentVideoWebsite) stopVideo(id int) {name, ok := t.videos[id]if !ok {fmt.Printf("视频%v不存在\n", id)return}fmt.Printf("暂停播放id为%v的视频%v\n", id, name)
}func (t *TencentVideoWebsite) seekVideo(id int, pos float64) {name, ok := t.videos[id]if !ok {fmt.Printf("视频%v不存在\n", id)}fmt.Printf("跳转id为%v的视频%v, 进度到%v\n", id, name, pos)
}

5.1.3 代理:实现

package _51tencent_video_proxyimport "fmt"// IMovieWebsite 私人电影网站
type IMovieWebsite struct {// 原始内容提供商, 是代理的对象: 如腾讯视频rawVideoWebsite VideoWebsite// 缓存的 videoscachedVideos map[int]string
}func NewIMovieWebsite(rawVideoWebsite VideoWebsite) VideoWebsite {return &IMovieWebsite{rawVideoWebsite: rawVideoWebsite,cachedVideos:    make(map[int]string),}
}// 私有方法, 拉取全部原始视频
func (im *IMovieWebsite) fetchRawVideos() {fmt.Println("[拉取原始视频列表] 开始")defer fmt.Println("[拉取原始视频列表] 结束")im.cachedVideos = im.rawVideoWebsite.listVideos()
}// 私有方法, 尝试拉取某原始视频
func (im *IMovieWebsite) fetchRawVideoIfNotExist(id int) {// fmt.Printf("[尝试拉取某原始视频%v] 开始\n", id)// defer fmt.Printf("[尝试拉取某原始视频%v] 结束\n", id)// 尝试从缓存中寻找_, ok := im.cachedVideos[id]if !ok {// 如果不存在则拉取im.fetchRawVideos()}
}func (im *IMovieWebsite) listVideos() map[int]string {// 如果无缓存, 则重新拉取if len(im.cachedVideos) == 0 {im.fetchRawVideos()}// 返回缓存的内容return im.cachedVideos
}func (im *IMovieWebsite) startVideo(id int) {im.fetchRawVideoIfNotExist(id)// 从缓存中寻找name, ok := im.cachedVideos[id]if !ok {fmt.Printf("视频%v不存在\n", id)return}fmt.Printf("开始播放id为%v的视频%v\n", id, name)
}func (im *IMovieWebsite) stopVideo(id int) {im.fetchRawVideoIfNotExist(id)// 从缓存中寻找name, ok := im.cachedVideos[id]if !ok {fmt.Printf("视频%v不存在\n", id)return}fmt.Printf("暂停播放id为%v的视频%v\n", id, name)
}func (im *IMovieWebsite) seekVideo(id int, pos float64) {im.fetchRawVideoIfNotExist(id)// 从缓存中寻找name, ok := im.cachedVideos[id]if !ok {fmt.Printf("视频%v不存在\n", id)return}fmt.Printf("跳转id为%v的视频%v, 进度到%v\n", id, name, pos)
}

5.1.4 单测

package _51tencent_video_proxyimport ("fmt""testing"
)// 第一次就拉取原始视频
/*
=== RUN   TestIMovieWebsite_FetchFirst
[拉取原始视频列表] 开始
[拉取原始视频列表] 结束
---StartVideo---
开始播放id为1的视频热辣滚烫
---stopVideo---
暂停播放id为1的视频热辣滚烫
---seekVideo---
跳转id为1的视频热辣滚烫, 进度到1.2
---StartVideo---
开始播放id为2的视频飞驰人生
---stopVideo---
暂停播放id为2的视频飞驰人生
---seekVideo---
跳转id为2的视频飞驰人生, 进度到1.2
--- PASS: TestIMovieWebsite_FetchFirst (0.00s)
PASS
*/
func TestIMovieWebsite_FetchFirst(t *testing.T) {tencentVideoWebsite := NewTencentVideoWebsite()iMovieWebsite := NewIMovieWebsite(tencentVideoWebsite)videos := iMovieWebsite.listVideos()for id := range videos {fmt.Println("---StartVideo---")iMovieWebsite.startVideo(id)fmt.Println("---stopVideo---")iMovieWebsite.stopVideo(id)fmt.Println("---seekVideo---")iMovieWebsite.seekVideo(id, 1.2)}
}// 不提前拉取原始视频, 而是懒加载
/*
=== RUN   TestIMovieWebsite_LazyFetch
---StartVideo---
[拉取原始视频列表] 开始
[拉取原始视频列表] 结束
开始播放id为1的视频热辣滚烫
---stopVideo---
暂停播放id为1的视频热辣滚烫
---seekVideo---
跳转id为1的视频热辣滚烫, 进度到1.2
---StartVideo---
开始播放id为2的视频飞驰人生
---stopVideo---
暂停播放id为2的视频飞驰人生
---seekVideo---
跳转id为2的视频飞驰人生, 进度到1.2
---StartVideo---
[拉取原始视频列表] 开始
[拉取原始视频列表] 结束
视频3不存在
---stopVideo---
[拉取原始视频列表] 开始
[拉取原始视频列表] 结束
视频3不存在
---seekVideo---
[拉取原始视频列表] 开始
[拉取原始视频列表] 结束
视频3不存在
--- PASS: TestIMovieWebsite_LazyFetch (0.00s)
PASS
*/
func TestIMovieWebsite_LazyFetch(t *testing.T) {tencentVideoWebsite := NewTencentVideoWebsite()iMovieWebsite := NewIMovieWebsite(tencentVideoWebsite)for _, id := range []int{1, 2, 3} {fmt.Println("---StartVideo---")iMovieWebsite.startVideo(id)fmt.Println("---stopVideo---")iMovieWebsite.stopVideo(id)fmt.Println("---seekVideo---")iMovieWebsite.seekVideo(id, 1.2)}
}

5.2 nginx

https://refactoringguru.cn/design-patterns/proxy/go/example

nginx 可作为 server 的代理:

  1. 控制可访问的 server 范围
  2. 限速
  3. 缓存请求
05proxy/052nginx
├── app.go
├── nginx.go
├── nginx_test.go
├── readme.md
└── server.go

5.2.1 Server

package _52nginx// Server 的接口, rawServerObject 和 proxyServerObject 均会实现此接口
type Server interface {handleRequest(method, url string) (code int, body string)
}

5.2.2 App

package _52nginx// app 是 Server
type app struct {
}func NewApp() Server {return &app{}
}func (a *app) handleRequest(method, url string) (code int, body string) {if method == "GET" && url == "/status" {return 200, "OK"}if method == "POST" && url == "/create/user" {return 201, "User Created"}return 404, "Not Found"
}

5.2.3 Nginx

package _52nginxtype Nginx struct {app Server// 次数限制器url2Cnt map[string]int// 各 URL 允许访问的最大次数maxCnt int
}func NewNginx(app Server, maxCnt int) Nginx {return Nginx{app:     app,url2Cnt: make(map[string]int),maxCnt:  maxCnt,}
}func (n *Nginx) handleRequest(method, url string) (code int, body string) {if !n.checkCntLimiterAllowed(method, url) {return 403, "Not Allowed"}return n.app.handleRequest(method, url)
}// 访问次数控制
func (n *Nginx) checkCntLimiterAllowed(method, url string) bool {// 次数超限if cnt := n.url2Cnt[url]; cnt >= n.maxCnt {return false}// 次数正常n.url2Cnt[url]++return true
}

5.2.4 nginx_test

package _52nginximport ("github.com/stretchr/testify/require""testing"
)// 测试超过最大的访问次数时, Nginx 的效果
func TestNginx(t *testing.T) {maxCnt := 2n := NewNginx(NewApp(), maxCnt)// 可被 App 匹配的 URL, 超过最大访问次数时, 被 Nginx 拦截for i := 0; i < 5; i++ {method, url := "GET", "/status"c, b := n.handleRequest(method, url)if i < maxCnt {// Nginx 放行, 实际由 App 响应require.EqualValues(t, 200, c)require.EqualValues(t, "OK", b)} else {// Nginx 拦截, 实际由 Nginx 响应require.EqualValues(t, 403, c)require.EqualValues(t, "Not Allowed", b)}}// 不被 App 匹配的 URL, 超过最大访问次数时, 被 Nginx 拦截for i := 0; i < 5; i++ {method, url := "POST", "/a-not-exist-url"c, b := n.handleRequest(method, url)if i < maxCnt {// Nginx 放行, 实际由 App 响应require.EqualValues(t, 404, c)require.EqualValues(t, "Not Found", b)} else {// Nginx 拦截, 实际由 Nginx 响应require.EqualValues(t, 403, c)require.EqualValues(t, "Not Allowed", b)}}
}

http://www.ppmy.cn/server/3610.html

相关文章

IDM的实用功能

IDM&#xff08;Internet Download Manager&#xff09;是一款用于加速下载的软件&#xff0c;具有许多实用功能&#xff0c;包括&#xff1a; 多线程下载&#xff1a;IDM支持多线程下载&#xff0c;可以同时下载多个文件&#xff0c;提高下载速度。 分段下载&#xff1a;IDM将…

论文阅读:BEVBert: Multimodal Map Pre-training for Language-guided Navigation

BEVBert&#xff1a;语言引导导航的多模态地图预训练 摘要 现存的问题&#xff1a;目前大多数现有的预训练方法都采用离散的全景图来学习视觉-文本关联。这要求模型隐式关联全景图中不完整、重复的观察结果&#xff0c;这可能会损害智能体的空间理解。 本文解决方案&#xf…

gitlab 16.2.4 恢复

新创建新gitlab&#xff0c;版本需和备份的gitlab版本一致 docker run -d -p 322:22 -p 822:80 --name gitlab_1 gitlab/gitlab-ce:16.2.4-ce.0 进入容器内部 docker exec -it c51685817e27 /bin/bash 新的环境至少运行过一次&#xff0c;sudo gitlab-ctl reconfigu…

STM32-ADC(独立模式、双重模式)

ADC简介 18个通道&#xff1a;外部信号源就是16个GPIO回。在引脚上直接接模拟信号就行了&#xff0c;不需要侄何额外的电路。引脚就直接能测电压。2个内部信号源是内部温度传感器和内部参考电压。 逐次逼近型ADC: 它是一个独立的8位逐次逼近型ADC芯片&#xff0c;这个ADC0809是…

Oracle数据库从入门到精通系列之二十:Linux上使用容器数据库(CDB)方式部署Oracle数据库19c详细步骤

@TOC 一、Oracle 数据库部署类型 Oracle数据库支持以下部署类型: 容器数据库(CDB) 可以包含多个可插入数据库 (PDB) 的数据库。数据库客户端连接到每个 PDB,就好像它是标准的非 CDB 数据库一样。非容器数据库(非CDB) 标准Oracle数据库,不支持创建可插拔数据库。二、安…

在linux系统中启动pycharm

1.找到pycharm的安装路径&#xff0c;一般在下载文件夹中 2.进入pycharm的安装路径&#xff0c;进入bin目录 3.右击&#xff0c;打开终端&#xff0c;输入./pycharm.sh

基于单片机恒温控制系统的开发研究

摘要:温度是工业生产的过程中最为常见的工艺参数,温度控制直接影响工业生产产品的质量,特别在机械、冶金、化工、建材、石油等工业生产中温度控制占据着重要的作用。虽然目前温度控制有多种方式,但是在专业化和高指标要求等方面还有待进一步开发研究。随着科学技术的进步,…

基于单片机的倒车预警系统设计

摘要:现代车辆工业中,对于倒车预警系统的需求度越来越高,因此必须要能够通过专业的控制系统,实现对倒车预警体系的科学设计与利用。基于对单片机设备作用下,倒车预警系统实现功能的分析,本文探讨了针对倒车预警系统的硬件和软件系统设计方法,并且论证了该体系的具体运行…