为另一个对象提供一个替身或占位符以控制对这个对象的访问。
使用代理模式创建代表对象,让代表对象控制某些对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象。(例如委托中介帮忙完成某项任务)
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
远程代理:远程代理是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。调用代理的方法,会被代理利用网络转发到远程执行,并且结果会通过网络返回代理,再由代理将结果转给客户。
虚拟代理:虚拟代理控制访问创建开销大的资源,对于一些占用系统资源较多或者加载时间较长的对象,可以给这些对象提供一个虚拟代理。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
保护代理:保护代理基于权限控制对资源的访问。
缓存代理:它为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,从而可以避免某些方法的重复执行,优化系统性能;经常用于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
}
装饰者为对象增加行为,代理是控制对象访问
代理和适配器都是挡在其他对象的前面,并负责将请求转发给它们。适配器会改变对象适配的接口,而代理则实现相同的接口