代理模式
概述
代理模式(Proxy Pattern)是一种结构型设计模式,它通过一个代理对象来控制对另一个对象(实际对象)的访问。代理对象充当客户端与实际对象之间的中介,可以在不直接访问实际对象的情况下,提供额外的功能,例如访问控制、延迟加载、日志记录、缓存等。代理模式的核心思想是“控制访问”。
模式结构
代理模式的主要角色如下:
- 抽象主题(Abstract Subject):定义代理和真实主题的公共接口,确保代理可以在任何使用真实主题的地方被使用。在Go中通常使用interface定义。
- 真实主题(Real Subject):实现抽象主题接口的结构体,定义了代理所代表的真实对象,包含实际的业务逻辑。
- 代理(Proxy):实现抽象主题接口的结构体,持有真实主题的引用,控制对真实主题的访问,并可在调用前后添加额外的处理逻辑。
- 客户端(Client):通过抽象主题接口与代理交互,无需知道是在与代理还是真实主题打交道,保持代码的透明性。
实现
代理模式的UML类图如下所示:
抽象主题(销售接口) sale.go 定义:
go
package proxy
// 代理模式 - 抽象主题
// Sale 销售接口
type Sale interface {
// SaleComputer 销售电脑
// money 价格
SaleComputer(money float64)
}
真实主题(经销商) seller.go 定义:
go
package proxy
import "fmt"
// 代理模式 - 真实主题
// Seller 经销商
// 真实主题角色,实现了 Sale 接口,负责实际的销售电脑逻辑
type Seller struct {
}
// SaleComputer 实现销售电脑的具体逻辑
// money 价格,表示客户支付的金额
func (s Seller) SaleComputer(money float64) {
fmt.Printf("销售电脑, 并拿到钱:%.2f", money)
}
代理(代理经销商) proxy.go 定义:
go
package proxy
import "fmt"
// 代理模式 - 代理
// Proxy 代理
// 代理角色,实现了 Sale 接口,控制对 Seller 的访问并添加额外逻辑
type Proxy struct {
seller *Seller // 持有对真实主题(Seller)的引用
}
// NewProxy 创建新的代理实例
// 返回一个初始化了 Seller 的 Proxy 实例
func NewProxy() *Proxy {
return &Proxy{
seller: &Seller{}, // 初始化真实主题
}
}
// SaleComputer 代理销售电脑,增加20%利润
// money 原始价格,代理会在此基础上计算利润并调用真实主题的逻辑
func (p *Proxy) SaleComputer(money float64) {
// 计算20%的利润
profit := money * 0.2
// 计算总价(原始价格 + 利润)
total := money + profit
// 打印代理的销售信息,包括原始价格、利润和总价
fmt.Printf("经销商代理销售电脑,原始价格: %.2f,增加20%%利润: %.2f,总价: %.2f\n", money, profit, total)
// 调用真实主题的销售逻辑
p.seller.SaleComputer(money)
}
客户端(单元测试) client_test.go 定义:
go
package proxy
import (
"bytes"
"io"
"os"
"testing"
)
// 单元测试
// 模拟客户端调用
// TestProxySaleComputer 测试代理模式的 SaleComputer 方法
func TestProxySaleComputer(t *testing.T) {
// 定义测试用例结构,包含测试名称、输入价格和预期输出
tests := []struct {
name string // 测试用例名称
money float64 // 输入价格
expected string // 预期输出
}{
{
name: "Test with price 10000", // 测试用例1:输入价格10000
money: 10000.0,
expected: "经销商代理销售电脑,原始价格: 10000.00,增加20%利润: 2000.00,总价: 12000.00\n销售电脑, 并拿到钱:10000.00",
},
{
name: "Test with price 5000", // 测试用例2:输入价格5000
money: 5000.0,
expected: "经销商代理销售电脑,原始价格: 5000.00,增加20%利润: 1000.00,总价: 6000.00\n销售电脑, 并拿到钱:5000.00",
},
{
name: "Test with price 0", // 测试用例3:输入价格0
money: 0.0,
expected: "经销商代理销售电脑,原始价格: 0.00,增加20%利润: 0.00,总价: 0.00\n销售电脑, 并拿到钱:0.00",
},
}
// 创建代理实例
proxy := NewProxy()
// 遍历所有测试用例
for _, tt := range tests {
// 使用 t.Run 运行子测试,tt.name 为子测试名称
t.Run(tt.name, func(t *testing.T) {
// 创建管道以捕获标准输出
r, w, _ := os.Pipe()
// 保存原始标准输出
old := os.Stdout
// 将标准输出重定向到管道
os.Stdout = w
// 确保测试结束后恢复标准输出
defer func() {
os.Stdout = old
}()
// 调用代理的 SaleComputer 方法
proxy.SaleComputer(tt.money)
// 关闭管道写入端
w.Close()
// 创建缓冲区以读取管道输出
var buf bytes.Buffer
// 将管道内容复制到缓冲区
io.Copy(&buf, r)
// 获取实际输出
output := buf.String()
// 格式化预期输出
expected := tt.expected
// 比较实际输出与预期输出
if output != expected {
t.Errorf("SaleComputer(%.2f) output = %q; want %q", tt.money, output, expected)
}
})
}
}
适用场景
延迟加载:例如延迟加载数据库连接或大文件。
访问控制:在微服务中,代理可以验证 API 请求的权限。
日志与监控:记录方法调用次数、执行时间等。
缓存:代理可以缓存频繁访问的数据,减少对真实对象的调用。
远程调用:在分布式系统中,代理可以封装网络通信逻辑。