代理模式(golang)

定义

为另一个对象提供一个替身或占位符以控制对这个对象的访问。

使用代理模式创建代表对象,让代表对象控制某些对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象。(例如委托中介帮忙完成某项任务)

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

场景

  • 远程代理:远程代理是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。调用代理的方法,会被代理利用网络转发到远程执行,并且结果会通过网络返回代理,再由代理将结果转给客户。

  • 虚拟代理:虚拟代理控制访问创建开销大的资源,对于一些占用系统资源较多或者加载时间较长的对象,可以给这些对象提供一个虚拟代理。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。

  • 保护代理:保护代理基于权限控制对资源的访问。

  • 缓存代理:它为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,从而可以避免某些方法的重复执行,优化系统性能;经常用于web服务器代理,缓存经常使用的内容,减少网络请求,提高并发

  • 防火墙代理:控制网络资源的访问,保护主题免于 “坏客户”的侵害。

  • 写入时复制代理:用来控制对象的复制,方法是延迟对象的复制,直到客户真的需要为止。这是虚拟代理的变体

类图

Proxy和RealSubject都实现了Subject接口,这允许任何客户都可以像处理RealSubject对象一样地处理proxy对象

RealSubject通常是真正做事的对象,proxy会控制对RealSubject的访问。

创建RealSubject对象,通常由proxy负责。

Proxy持有Subject的引用,所以必要时它可以将请求转发给Subject。

客户和RealSubject的交互都必须通过Proxy,因为Proxy和RealSubject实现相同的接口Subject,所以任何用到RealSubject的地方,都可以用Proxy取代。

实现

  • 远程代理

远程代理就像我们使用的RPC服务一样,可以从一台计算机上执行另外一台计算机上的程序。

   

     

PS:我们通过远程调就可以设置设备状态,不需要到设备现场进行操作

真实设置设备状态的方法(被代理方)

package server

import (
    "log"
    "net"
    "net/http"
    "net/rpc"
)

type Args struct {
    Name, Status string
}

type Device struct{}

func (d *Device) SetDeviceStatus(args *Args, reply *string) error {
    *reply = "设置" + args.Name + "状态为" + args.Status
    return nil
}

func Run() {
    device := new(Device)
    rpc.Register(device)
    rpc.HandleHTTP()
    l, e := net.Listen("tcp", ":1234")
    if e != nil {
        log.Fatal("listen error:", e)
    }
    http.Serve(l, nil)
}

代理设置设备状态的方法(proxy)

package proxy

import (
    "fmt"
    "log"
    "net/rpc"
)

type Args struct {
    Name, Status string
}

type Device struct{}

func SetDeviceStatus(name,status string) {
    client, err := rpc.DialHTTP("tcp", "127.0.0.1:1234")
    if err != nil {
        log.Fatal("dialing:", err)
    }

    args := Args{
        Name:   "设备一",
        Status: "开启",
    }
    var reply *string
    err = client.Call("Device.GetStatus", args, &reply)
    if err != nil {
        log.Fatal("device error:", err)
    }
    fmt.Println(*reply)
}

用户只需要通过远程代理即可设置设备的状态

package main

import (
    "test/proxy"
)

func main() {
    proxy.SetDeviceStatus("设备一","开启")
}
  • 虚拟代理

PS:我们将创建一个Image接口和实现了Image接口的实体类。ProxyImage是一个代理类,在加载真实图片成功之前,我们提供一个虚拟图片代理真实图片,当真实图片加载成功后,虚拟代理将请求转发到真实对象,返回真实图片。

package proxy

import (
    "fmt"
    "time"
)

type Image interface {
    Display()
}

// 真实
type RealImage struct {
    fileName string
}

func (r *RealImage) Display() {
    fmt.Println("Displaying " + r.fileName)
}

func (r *RealImage) Load(fileName string) {
    time.Sleep(2 * time.Second) //加载耗时很长的样子
    fmt.Println("Loading " + r.fileName)
}

func NewRealImage(fileName string) *RealImage {
    realImage := new(RealImage)
    realImage.fileName = fileName
    realImage.Load(fileName)
    return realImage
}

// 代理
type ProxyImage struct {
    fileName  string
    realImage *RealImage
}

func (r *ProxyImage) Display() {
    if r.realImage == nil {
        fmt.Println("加载中....") //代理默认图片
    }
    r.realImage = NewRealImage(r.fileName)
    r.realImage.Display()
}

func NewProxyImage(fileName string) *ProxyImage {
    realImage := new(ProxyImage)
    realImage.fileName = fileName
    return realImage
}
  • 保护代理

RealSubject和Proxy都实现了接口Subject,RealInvocationHandler实现了InvocationHandler接口,Proxy上的任何方法调用都会被传入此类。RealInvocationHandler控制对象RealSubject方法的访问。

PS:我们想从mysql中通过HandleData方法获取数据,在AppHandler中增加了优化查询逻辑,当redis中无对应数据时才到mysql中查询数据。这就对DBHandler 的访问进行可控制。

package main

import (
    "fmt"
)

type Handler interface {
    HandleData(string) (interface{}, error)
}

type DBHandler struct {
    DBname string
}
// 真实数据存放在mysql
func (db *DBHandler) HandleData(data string) (interface{}, error) {
    fmt.Println("从数据库获取数据成功", data)
    return nil, nil
}

type AppHandler struct {
    Handler *DBHandler
}
// 代理控制增强了查询
func (app *AppHandler) HandleData(data string) (interface{}, error) {
    res := GetDataFromRedis() //从redis获取数据
    if res {
        fmt.Println("从redis获取数据成功")
    } else {
        // 获取不到从DB中获取数据
        app.Handler.HandleData(data)
        // 将数据存储到redis
        StoreDataToRedis()
    }
    return nil, nil
}

func GetDataFromRedis() bool {
    return true
}

func StoreDataToRedis() {
    fmt.Println("成功将数据存入到redis中")
}

func main() {
    var handler Handler
    handler = &AppHandler{Handler: &DBHandler{DBname: "mysql"}}
    handler.HandleData("username")
    return
}

优缺点

  • 优点:

  1. 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
  2. 代理对象可以扩展目标对象的功能
  3. 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性
  • 缺点:

  1. 代理模式会造成系统设计中类的数目增加
  2. 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢
  3. 增加系统的复杂度

使用场景

  • 保护目标对象。增强目标对象

与其他模式差异

  • 装饰者:

装饰者为对象增加行为,代理是控制对象访问

  • 适配器:

代理和适配器都是挡在其他对象的前面,并负责将请求转发给它们。适配器会改变对象适配的接口,而代理则实现相同的接口

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值