代理模式
类型
结构型设计模式
核心思想
代理模式的核心思想是在不改变原始对象的前提下,通过代理对象控制原始对象的访问,实现对原始对象的一定程度的限制或增强。
概述
在代理模式中,代理对象和原始对象都要实现相同的接口,原始对象作为代理对象中的一个成员,通过代理对象来间接地访问原始对象,并在访问前后添加额外的逻辑。代理模式主要解决的问题是在不改变原有代码的情况下,增加额外的逻辑功能。
结构图
主要角色
- 抽象主题(Subject):定义了真实主题和代理主题的公共接口,客户端可以通过抽象主题来访问真实主题或代理主题。
- 原始对象(Real Subject):实现抽象主题接口的类,是业务逻辑的具体实现
- 代理对象(Proxy Subject):也实现了抽象主题接口,并且保存了一个只想真实主题对象的引用,代理主题通常在客户端无法直接访问真实主题或者需要增强真实主题的功能时使用,它可以对真实主题的方法进行拓展、过滤或者控制访问。
使用场景
- 远程代理:客户端可以通过代理对象访问位于远程服务器上的对象。
- 虚拟代理(延迟加载):当创建对象需要很大的开销时,可以先创建一个代理对象,等到真正需要使用对象时再去创建它。例如,一些图形应用程序可能需要在窗口中显示大量的图像,但是在窗口初始化时就将所有图像都加载到内存中可能会导致性能问题。这种情况下,使用虚拟代理可以在图像需要显示时才进行加载,从而节省内存和提高性能。
- 安全代理:代理对象可以拦截一些请求,进行权限校验,以确保客户端有权访问目标对象。
- 智能代理:代理对象可以在调用目标对象前后进行一些额外的操作,例如缓存、日志记录、性能监控等等。
- 动态代理:通过动态生成代理类的方式,可以在运行时对目标对象进行代理,这种方式可以更加灵活。
优点
- 实现对象的控制访问:通过代理对象控制对真实对象的访问,在不改变真实对象的前提下增加一些额外的功能,如记录日志、缓存等。
- 实现远程访问:通过代理对象可以将远程对象调用本地化,可以方便地调用远程服务。
- 实现虚拟对象:通过代理对象可以创建一个虚拟对象,可以在不需要真实对象的情况下完成一些操作,延迟创建一些开销较大的真实对象,从而提高系统的性能。
- 实现安全控制:可以通过代理对象控制对真实对象的访问,从而实现安全控制。
缺点
- 增加系统的复杂度:引入了代理类之后,会增加一些类和对象,导致系统变得更加复杂。
- 降低系统的性能:因为在客户端和真实对象之间增加了一层代理对象,所以请求的处理速度可能会降低。
- 增加系统的开销:由于在客户端和真实对象之间增加了一层代理对象,所以在一定程度上增加了系统的开销,可能会导致系统的性能下降。
- 可能会出现调试困难的情况:如果系统中存在多层代理,那么在调试过程中可能会出现困难,因为我们不知道究竟是哪一层代理出现了问题。
实现方法
代理模式有两种实现方式,为静态代理和动态代理
静态代理
在编译期间就已经确定代理对象的实现,代理对象和被代理对象实现相同的接口,代理对象在编译期间就已经确定了被代理对象的身份,因此在程序运行过程中无法动态地改变被代理对象。
动态代理
动态代理的关键在于在运行时动态创建代理类,并且代理类实现了与被代理对象相同的接口,因此可以接收实现该接口的任何对象并代理其方法。通过golang的反射机制来实现
demo(golang)
静态代理
抽象主题
// 计算器接口
type Calculator interface {
Add(a, b int) int
}
原始对象
// 实际的计算器实现
type RealCalculator struct{}
func (c *RealCalculator) Add(a, b int) int {
return a + b
}
代理对象
// 计算器代理
type CalculatorProxy struct {
realCalculator *RealCalculator
logger *log.Logger
}
func NewCalculatorProxy(realCalculator *RealCalculator) *CalculatorProxy {
return &CalculatorProxy{
realCalculator: realCalculator,
logger: log.New(os.Stdout, "", log.LstdFlags),
}
}
func (p *CalculatorProxy) Add(a, b int) int {
p.logger.Printf("Add operation: a=%d, b=%d", a, b)
result := p.realCalculator.Add(a, b)
p.logger.Printf("Result: %d", result)
return result
}
实际调用
func main() {
// 创建实际的计算器对象
calculator := &RealCalculator{}
// 创建计算器代理
calculatorProxy := NewCalculatorProxy(calculator)
// 使用代理对象进行计算
calculatorProxy.Add(1, 2)
}
代理模式与外观模式的区别
外观模式:为了向客户端隐藏复杂的子系统,提供一个简单的接口,使得客户端可以更方便地使用子系统。 代理模式:控制对原始对象的访问,并在访问前后添加额外的逻辑。 区分好目的,一个是隐藏系统复杂度,一个是添加对象访问前后的逻辑。