代理模式(Proxy Pattern)是一种结构型设计模式,它允许对象通过代理来控制对其它对象的访问。在代理模式中,代理对象与实际对象具有相同的接口,因此代理对象可以替代实际对象使用,而且客户端不需要知道实际对象的存在。代理对象负责与实际对象交互,它可以在调用实际对象之前或之后执行一些操作,如验证权限、缓存数据等。
实现方式:
远程代理(Remote Proxy):用于控制远程对象的访问,它隐藏了远程对象的具体实现方式,客户端只需要知道代理对象的存在即可。
虚拟代理(Virtual Proxy):用于控制创建昂贵的对象,它可以延迟对象的创建直到真正需要时再创建,从而减少资源的消耗。
保护代理(Protection Proxy):用于控制对对象的访问权限,它可以根据客户端的权限来限制对象的访问。
缓存代理(Cache Proxy):用于缓存对象,避免重复创建对象,从而提高性能。
安全代理(Protection Proxy):控制原始对象的访问权限,在调用真实对象的方法之前进行权限检查,以确保调用者有足够的权限进行操作
智能指引(Smart Reference):当一个对象被引用时,提供一些额外的操作,如对象的计数器,记录对象的引用次数,以便于在没有任何引用时,可以自动清除对象。
延迟加载(Lazy Load):延迟加载是指,只有当需要访问一个对象的属性或者方法时,才会真正的创建这个对象。当访问的对象非常大或者加载速度较慢时,延迟加载可以显著提高程序的性能。
代理模式通常用于需要对某个对象进行控制或隐藏的情况下,实现客户端和服务端的解耦,增强可扩展性
示例
// 定义主题接口
type Subject interface {
Request() string
}
// 定义实际主题
type RealSubject struct{}
func (s *RealSubject) Request() string {
return "RealSubject Request"
}
// 定义代理主题
type ProxySubject struct {
subject *RealSubject
}
func (p *ProxySubject) Request() string {
// 在实际主题方法调用之前进行一些操作
if p.subject == nil {
p.subject = &RealSubject{}
}
result := p.subject.Request()
// 在实际主题方法调用之后进行一些操作
return result
}
func main() {
// 使用代理对象来调用实际主题方法
var s Subject = &ProxySubject{}
fmt.Println(s.Request())
}
远程代理
// 定义远程对象
type RemoteObject struct {}
// 远程对象的方法
func (o *RemoteObject) Method1(arg1 int, arg2 string) (string, error) {
// 方法实现
return "", nil
}
// 服务端
func main() {
// 注册远程对象
remoteObject := new(RemoteObject)
rpc.Register(remoteObject)
// 启动服务
listener, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal("listen error:", err)
}
rpc.Accept(listener)
}
// 客户端
func main() {
// 连接服务
client, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
log.Fatal("dialing:", err)
}
// 调用远程方法
var result string
err = client.Call("RemoteObject.Method1", []interface{}{1, "arg2"}, &result)
if err != nil {
log.Fatal("error:", err)
}
// 处理结果
fmt.Println(result)
}
分布式缓存
提供统一的接口,先GET本地缓存、再请求远程
// 定义缓存接口
type Cache interface {
Get(key string) (interface{}, error)
Set(key string, value interface{}) error
}
// 定义本地缓存实现
type LocalCache struct {
cache map[string]interface{}
}
func NewLocalCache() *LocalCache {
return &LocalCache{cache: make(map[string]interface{})}
}
func (lc *LocalCache) Get(key string) (interface{}, error) {
if val, ok := lc.cache[key]; ok {
return val, nil
}
return nil, errors.New("key not found")
}
func (lc *LocalCache) Set(key string, value interface{}) error {
lc.cache[key] = value
return nil
}
// 定义远程缓存代理
type RemoteCacheProxy struct {
cache Cache
url string
}
func NewRemoteCacheProxy(url string) *RemoteCacheProxy {
return &RemoteCacheProxy{cache: nil, url: url}
}
func (r *RemoteCacheProxy) Get(key string) (interface{}, error) {
if r.cache == nil {
// 延迟初始化
client := http.DefaultClient
resp, err := client.Get(r.url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("server returned: %v", resp.Status)
}
cache := NewLocalCache()
err = json.NewDecoder(resp.Body).Decode(&cache.cache)
if err != nil {
return nil, err
}
r.cache = cache
}
return r.cache.Get(key)
}
func (r *RemoteCacheProxy) Set(key string, value interface{}) error {
// 省略实现
return nil
}
// 主函数
func main() {
remoteCacheProxy := NewRemoteCacheProxy("http://remote-cache-server/cache")
val, err := remoteCacheProxy.Get("key")
if err != nil {
log.Fatal(err)
}
fmt.Println(val)
}
虚拟代理
虚拟代理是代理模式的一种应用场景,它通常在需要创建代理对象的成本很高或者某些代理对象只在特定条件下才需要被创建时使用,可以通过使用虚拟代理来实现延迟对象的创建,即在真正需要使用某个资源时才进行加载,而在此之前使用一个代理对象来代替实际的资源对象,以达到优化性能的目的。
下面是一个使用虚拟代理模式的示例,实现一个图片加载器,其中代理模式用于延迟图片的加载,直到图片真正需要被显示时才进行加载
// Image interface定义了图片加载的基本操作
type Image interface {
Display()
}
// RealImage实现了Image接口,并实现了真正的图片加载操作
type RealImage struct {
filename string
}
func NewRealImage(filename string) *RealImage {
return &RealImage{filename: filename}
}
func (r *RealImage) Display() {
fmt.Println("Displaying Real Image: " + r.filename)
}
// ProxyImage实现了Image接口,并实现了代理对象的加载操作
type ProxyImage struct {
filename string
realImage *RealImage
}
func NewProxyImage(filename string) *ProxyImage {
return &ProxyImage{filename: filename}
}
func (p *ProxyImage) Display() {
if p.realImage == nil {
p.realImage = NewRealImage(p.filename)
}
p.realImage.Display()
}
// 使用代理模式实现图片加载
func main() {
image1 := NewProxyImage("test1.jpg")
image2 := NewProxyImage("test2.jpg")
// 图片并未被真正加载
image1.Display()
image2.Display()
// 图片被真正加载
image1.Display()
image2.Display()
}
在这个示例中,RealImage代表真正的图片加载器,而ProxyImage代表虚拟代理对象。在ProxyImage的Display方法中,首先判断realImage是否已经被加载,如果没有被加载,则进行加载操作,并将realImage赋值给代理对象。这样,在多次调用Display方法时,实际上只有第一次会进行图片加载,后续调用会直接使用已经加载好的图片。这样在需要的时候再去延迟创建,减少程序的开销
安全代理
安全代理是代理模式的一种常见使用场景,主要用于保护敏感对象,控制对敏感对象的访问权限。
以下是使用代理模式实现安全代理的示例代码,通过代理类控制对真实对象的访问权限:
package main
import (
"fmt"
)
// 定义敏感对象接口
type SensitiveObject interface {
Operation() string
}
// 实现敏感对象接口的真实对象
type RealObject struct {
}
func (ro *RealObject) Operation() string {
return "Sensitive Operation"
}
// 代理类
type Proxy struct {
realObject *RealObject
}
func NewProxy() *Proxy {
return &Proxy{realObject: &RealObject{}}
}
func (p *Proxy) Operation() string {
if p.CheckAccess() {
return p.realObject.Operation()
}
return "Access Denied"
}
func (p *Proxy) CheckAccess() bool {
// 在这里可以加入访问权限的控制逻辑,例如根据用户权限判断是否有权限访问敏感对象等
return false
}
func main() {
// 创建代理对象
proxy := NewProxy()
// 访问敏感对象
fmt.Println(proxy.Operation()) // Output: Access Denied
}
在上述代码中,SensitiveObject 定义了敏感对象接口,其中 Operation() 方法是敏感对象的核心操作。RealObject 类实现了 SensitiveObject 接口,它是真正的敏感对象。
Proxy 类作为代理类,包含一个 RealObject 类型的指针,它代理了真正的敏感对象。Operation() 方法中的 CheckAccess() 方法用于控制访问权限,根据访问权限的不同返回相应的结果。如果权限验证通过,Operation() 方法就会调用真实对象的 Operation() 方法。
在 main() 函数中,我们创建了一个代理对象 proxy,并尝试访问敏感对象,可以看到输出结果为 "Access Denied",说明代理对象在访问敏感对象时进行了权限验证。
智能引用代理
例一
一个使用智能引用代理的场景是缓存,通过代理对象访问实际对象前,代理对象可以检查缓存中是否已经存在实际对象,如果存在则直接返回缓存的对象,否则再去访问实际对象。这样可以提高访问效率,避免多次访问实际对象,减少资源消耗。
package main
import "fmt"
// Subject 是实际对象和代理对象共同实现的接口
type Subject interface {
Request() string
}
// RealSubject 是实际对象,用于执行实际的操作
type RealSubject struct {
data string
}
func (rs *RealSubject) Request() string {
fmt.Println("执行实际操作")
return rs.data
}
// Proxy 是代理对象,用于执行一些额外操作,例如缓存等
type Proxy struct {
realSubject *RealSubject
cache string
}
func (p *Proxy) Request() string {
if p.cache != "" {
fmt.Println("返回缓存数据")
return p.cache
}
// 如果缓存中不存在,则访问实际对象
if p.realSubject == nil {
p.realSubject = &RealSubject{"真实数据"}
}
result := p.realSubject.Request()
// 将访问结果缓存起来
p.cache = result
return result
}
func main() {
// 使用代理对象访问实际对象
proxy := &Proxy{}
fmt.Println(proxy.Request())
// 再次使用代理对象访问实际对象,此时会返回缓存的数据
fmt.Println(proxy.Request())
}
在这个示例代码中,Subject接口是实际对象和代理对象共同实现的接口,RealSubject是实际对象,用于执行实际的操作,Proxy是代理对象,用于执行一些额外操作,例如缓存等。当客户端通过代理对象访问实际对象时,代理对象会在访问实际对象之前或之后执行指定的操作,这里是先检查缓存中是否已经存在实际对象,如果存在则直接返回缓存的对象,否则再去访问实际对象。这样就实现了一个简单的缓存功能。
例二
智能指引代理模式可以在程序运行时为客户端提供额外的指引或辅助功能,例如记录日志、缓存或延迟加载等。在实现智能指引代理模式时,需要先定义一个代理类来实现与被代理对象相同的接口。代理类在接收到客户端请求时,可以在完成请求前或完成请求后添加额外的处理逻辑,例如记录请求日志、启用缓存等
// 定义被代理对象的接口
type Subject interface {
Request() string
}
// 实现被代理对象的类
type RealSubject struct{}
func (r *RealSubject) Request() string {
return "RealSubject request"
}
// 定义代理类
type Proxy struct {
realSubject *RealSubject
}
func (p *Proxy) Request() string {
// 在完成请求前添加额外的处理逻辑
log.Printf("Proxy received request at %s", time.Now().String())
// 创建被代理对象
if p.realSubject == nil {
p.realSubject = &RealSubject{}
}
// 调用被代理对象的方法
result := p.realSubject.Request()
// 在完成请求后添加额外的处理逻辑
log.Printf("Proxy returned response at %s", time.Now().String())
return result
}
在上述代码中,定义了一个 Subject 接口和一个实现了该接口的 RealSubject 类,其中 Request() 方法返回一个字符串。
接着定义了一个代理类 Proxy,实现了 Subject 接口,代理类内部维护了一个 RealSubject 对象。在代理类的 Request() 方法中,首先添加了记录请求日志的处理逻辑,然后判断是否已经创建了被代理对象,如果没有则创建,最后调用被代理对象的方法并将其返回值作为代理方法的返回值,同时添加了记录响应日志的处理逻辑。
例三
资源的释放,即当对象使用完毕后,代理可以自动释放资源而无需手动处理。在这种情况下,代理会在调用对象的方法之前或之后,执行某些操作。
下面是一个简单的示例,展示如何使用代理模式实现自动释放资源:
package main
import "fmt"
type Resource interface {
DoSomething()
}
type ResourceImpl struct{}
func (r *ResourceImpl) DoSomething() {
fmt.Println("Doing something...")
}
type ResourceProxy struct {
resource *ResourceImpl
}
func (p *ResourceProxy) DoSomething() {
if p.resource == nil {
p.resource = &ResourceImpl{}
}
defer p.resource.Release()
p.resource.DoSomething()
}
func (r *ResourceImpl) Release() {
fmt.Println("Releasing resource...")
}
func main() {
proxy := &ResourceProxy{}
proxy.DoSomething()
}
在上面的示例中,我们定义了一个 Resource 接口和其实现 ResourceImpl。ResourceProxy 是 ResourceImpl 的代理,并在 DoSomething 方法中实现了自动释放资源的功能。当 DoSomething 方法被调用时,代理会创建 ResourceImpl 的实例并调用其 DoSomething 方法。当方法执行完毕后,代理会在 defer 语句中调用 Release 方法,以释放资源。
当我们运行程序时,将会看到以下输出:
Doing something...
Releasing resource...
从输出中可以看出,代理成功地执行了自动释放资源的功能。
需要注意的是,在实际生产环境中,可能需要更加复杂的逻辑来确保资源的正确释放和管理。
延迟加载
代理模式的一种使用场景是延迟加载(Lazy Loading),即在需要使用某个对象时才进行加载,而不是在程序启动时一次性加载所有对象。这种方式可以优化程序的启动速度和内存占用。
在代理模式中,可以通过虚拟代理实现延迟加载。当客户端首次请求对象时,代理会加载对象并保存在内存中,以后的请求直接使用已加载的对象。
package main
import "fmt"
// 定义接口
type Image interface {
Display()
}
// 实现接口的具体类
type RealImage struct {
Filename string
}
func (i *RealImage) Display() {
fmt.Println("Displaying " + i.Filename)
}
// 代理类
type ProxyImage struct {
realImage *RealImage
Filename string
}
func (i *ProxyImage) Display() {
if i.realImage == nil {
i.realImage = &RealImage{Filename: i.Filename}
}
i.realImage.Display()
}
func main() {
// 首次请求图片,需要加载
image1 := &ProxyImage{Filename: "image1.png"}
image1.Display()
// 再次请求同一张图片,直接使用已加载的对象
image2 := &ProxyImage{Filename: "image1.png"}
image2.Display()
}
在上面的例子中,RealImage是实现Image接口的具体类,代表实际的图片对象。ProxyImage是代理类,也实现了Image接口,它持有一个RealImage对象的引用。当客户端首次请求一张图片时,ProxyImage会创建一个RealImage对象,并将其保存在内存中;以后再次请求同一张图片时,ProxyImage直接使用已加载的对象,避免了重复加载。