亦称: Proxy

背景

老代码中,调用了某个类去实现特定功能。新的需求:需要在调用前后增加其它的操作步骤。

为此去修改被调用的类或者客户端代码,都会对原有代码带来较大的修改。

解决方案

代理模式建议新建一个与原服务对象接口相同代理类, 然后应用将代理对象传递给所有原始对象客户端。

代理类接收到客户端请求后会创建实际的服务对象, 并将所有工作委派给它。

代理模式能够在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。

一般代理类和被代理类有同一个父类或实现同一个接口。

代理模式

  • 代理类Proxy和原始类Service实现了相同接口ServiceInterface,代理类包含被代理类对象的引用。使用Service实例的地方都可以使用Proxy。
    • 代理类定义时,包含的引用可以是ServiceInterface类型,这样就可以代理不同的具体类。
  • 代理类在被代理类外面套了一层壳。请求Service的时候,必须经过Proxy,这样一些前置或者后置的通用操作可以放在Proxy中,扩展性和通用性都很强。
  • 因为Proxy和Service各自实现了一部分功能,会使Service更关注自己的业务逻辑,起到隔离的效果。
  • 使用时,客户端调Proxy,Proxy调原始类。

使用场景

  • 延迟初始化(虚拟代理)。如果有一个偶尔使用的重量级服务对象,一直保持该对象运行会消耗系统资源时可使,用代理模式。
    • 无需在程序启动时就创建该对象,将对象的初始化延迟到真正有需要的时候。
  • 访问控制 (保护代理)。如果只希望特定客户端使用服务对象,此时可使用代理模式。
    • 代理可仅在客户端凭据满足要求时将请求传递给服务对象
  • 本地执行远程服务 (远程代理)。适用于服务对象位于远程服务器上的情形
    • 代理通过网络传递客户端请求,负责处理所有与网络相关的复杂细节
  • 记录日志请求 (日志记录代理)。适用于需要保存对于服务对象的请求历史记录时
    • 代理可以在向服务传递请求前进行记录。
  • 缓存请求结果 (缓存代理)。适用于需要缓存客户请求结果并对缓存生命周期进行管理时,特别是当返回结果的体积非常大时。
    • 代理可对重复请求所需的相同结果进行缓存,还可使用请求参数作为索引缓存的键值。
  • 智能引用。可在没有客户端使用某个重量级对象时立即销毁该对象
    • 代理会将所有获取了指向服务对象或其结果的客户端记录在案。代理会时不时地遍历各个客户端,检查它们是否仍在运行。如果相应的客户端列表为空,就会销毁该服务对象,释放底层系统资源。

具体场景

  • 业务系统的非功能性需求开发,如监控、统计、鉴权、限流、事务、幂等、日志等,这些和业务没有关系,可以放在Proxy中,Service类只关注功能性需求
  • 框架设计,如RPC框架的实现。调用RPC客户端,客户端会自动调用RPC服务端,客户端也是一个代理,做了大量操作让开发者不用关心是如何成功调用到服务端的,只需要关心逻辑实现即可。

实现步骤

  1. 如果没有现成的服务接口,就需要创建一个接口来实现代理和服务对象的可交换性。
    • 备选方案是将代理作为服务类的子类, 这样代理就能继承服务的所有接口了。
  2. 创建代理类,其中必须包含一个指向服务引用的成员变量。
    • 代理负责创建服务并对其整个生命周期进行管理。
    • 客户端也可以通过构造函数将服务传递给代理。
  3. 根据需求实现代理方法
    • 在大部分情况下,代理在完成一些任务后应将工作委派给服务对象。
  4. 可以考虑为服务对象实现延迟初始化

优缺点

优点

  • 可以在客户端毫无察觉的情况下控制服务对象。
  • 如果客户端对服务对象的生命周期没有特殊要求,可以实现服务对象生命周期的管理。
  • 即使服务对象还未准备好或不存在,代理也可以正常工作。
  • 开闭原则。你可以在不对服务或客户端做出修改的情况下创建新代理。

缺点

  • 代码可能会变得复杂, 因为需要新建许多类。
  • 服务响应可能会延迟。

与其它模式的关系

  • 适配器模式能为被封装对象提供不同的接口,代理模式能为对象提供相同的接口,装饰模式则能为对象提供加强的接口。
  • 外观模式与代理的相似之处在于它们都缓存了一个复杂实体并自行对其进行初始化。
    • 代理与其服务对象遵循同一接口, 使得自己和服务对象可以互换, 在这一点上它与外观不同。
  • 装饰和代理有着相似的结构, 但是其意图却非常不同
    • 这两个模式的构建都基于组合原则, 一个对象将部分工作委派给另一个对象。
    • 两者之间的不同之处在于代理通常自行管理其服务对象的生命周期,而装饰的生成则总是由客户端进行控制。

示例

示例一

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main

import "fmt"

// PaymentService 支付接口
type PaymentService interface {
	pay(order string) string
}

// WXPay wx支付,被代理类
type WXPay struct {
}

func (*WXPay) pay(order string) string {
	return "wx token"
}

// AliPay ali支付,被代理类
type AliPay struct {
}

func (*AliPay) pay(order string) string {
	return "ali token"
}

// PaymentProxy 代理类,需要组合一个被代理类的对象,通常用接口
type PaymentProxy struct {
	realPay PaymentService // 注意PaymentService是接口,不要再用引用
}

func NewPaymentProxy(realPay PaymentService) *PaymentProxy {
	return &PaymentProxy{realPay: realPay}
}

// pay 代理类也实现了PaymentService接口
func (p *PaymentProxy) pay(order string) string {
	fmt.Println("支付前处理:" + order)
	token := p.realPay.pay(order)
	return fmt.Sprintf("获得token %s\n", token)
}

func main() {
	proxy := NewPaymentProxy(new(AliPay))
	url := proxy.pay("阿里订单")
	fmt.Println(url)
}

示例二

1
2
3
4
// Server 服务接口
type Server interface {
	HandleRequest(string, string) (int, string)
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15

// Application 服务主体,被代理对象
type Application struct {
}

func (a *Application) HandleRequest(url, method string) (int, string) {
	if url == "/app/status" && method == "GET" {
		return 200, "Ok"
	}

	if url == "/create/user" && method == "POST" {
		return 201, "User Created"
	}
	return 404, "Not Ok"
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Nginx 代理
type Nginx struct {
	application       Server
	maxAllowedRequest int
	rateLimiter       map[string]int
}

func NewNginxServer() *Nginx {
	return &Nginx{
		application:       &Application{},
		maxAllowedRequest: 2,
		rateLimiter:       make(map[string]int),
	}
}

func (n *Nginx) HandleRequest(url, method string) (int, string) {
	allowed := n.checkRateLimiting(url)
	if !allowed {
		return 403, "Not Allowed"
	}
	// 调用被代理对象
	return n.application.HandleRequest(url, method)
}

func (n *Nginx) checkRateLimiting(url string) bool {
	n.rateLimiter[url] = n.rateLimiter[url] + 1

	if n.rateLimiter[url] > n.maxAllowedRequest {
		return false
	}
	return true
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import "fmt"

func main() {

	// 构造代理类,内部关联被代理对象
	nginxServer := NewNginxServer()
	appStatusURL := "/app/status"
	createuserURL := "/create/user"

	format := "\nUrl: %s, HttpCode: %d, Body: %s"

	httpCode, body := nginxServer.HandleRequest(appStatusURL, "GET")
	fmt.Printf(format, appStatusURL, httpCode, body)

	httpCode, body = nginxServer.HandleRequest(appStatusURL, "GET")
	fmt.Printf(format, appStatusURL, httpCode, body)

	httpCode, body = nginxServer.HandleRequest(appStatusURL, "GET")
	fmt.Printf(format, appStatusURL, httpCode, body)

	httpCode, body = nginxServer.HandleRequest(createuserURL, "POST")
	fmt.Printf(format, appStatusURL, httpCode, body)

	httpCode, body = nginxServer.HandleRequest(createuserURL, "GET")
	fmt.Printf(format, appStatusURL, httpCode, body)
}

References

guru